|
K.V. Viswanathan is a Curriculum Development Manager at ObjectShare, Inc., Santa Ana, CA. He can
be contacted at [email protected].
THE MAIN PILLARS on which Java stands are platform independence and object orientation.
While the developer community has been very quick to exploit the former, it has either been largely
blind to the latter or is insufficiently tutored in the principles of object orientation to exploit
it effectively. Java is gathering momentum, and unless there is widespread commitment to utilizing
the object-oriented features of the language, the world will rapidly accumulate a mass of poorly
written Java code. I provide here some pointers to developers through an example. My chief aim is
to motivate Java programmers to exploit the object-oriented features of the language.
The Java Programming language has, in a very short time, evolved from being a "nice idea" to one
that nearly every organization is very seriously evaluating as a possible universal platform of
the future. The language itself has been evolving rapidly, with significant enhancements appearing
at regular intervals. Issues identified as bottlenecks to widespread adoption are being addressed
very seriously. Java is most definitely here to stay, and, over the next few years, it is very
likely that more Java code will be written than code in any other programming language.
The developer community that is migrating to Java is doing so from a variety of backgrounds.
A significant percentage is likely to be doing so from a non-object-oriented background. A
steep learning curve is associated with object-orientation. A look at the current Java tutorial
books available, quickly reveals that the focus is on language syntax and on using its class
library. Almost no attention is being paid to teaching good object-oriented programming principles.
Development organizations are jumping into Java projects without preparing the ground to reap the
benefits of object orientation. Just adopting Java as the platform is certainly not guaranteed to
bring in the benefits of object orientation. Several authors have pointed out that O-O is not
a "silver bullet." Every Java developer needs to be committed to exploiting the full power of
the O-O features in Java. Combined with its extensive and growing class library, and the rapid
enhancements being made in dynamic compilation, it will become a force to reckon with in the
very near future. It will be in the best interest of the IS community and every Java developer
to prepare for the coming revolution by appreciating the key issues involved.
My aim is to provide some of the conceptual inputs, and, through an example, excite Java developers
into making a serious commitment to "O-O development in Java" rather than just "Java development."
We'll take a very simple application and delve into how to view even this simple application from
an object-oriented perspective. The problem we consider is intentionally very simple because we
do not want to cloud the issues being discussed by introducing other sources of complexity. The
lessons to be learned are universal and can be applied to all problems, large or small.
THE PROBLEM
Consider the problem of taking a number and determining whether it is a prime number or not
(a number is said to be prime if it is divisible only by itself and 1). Object orientation
is about programming in the large, and thus we will not focus on the actual algorithm for
determining primality—that is the subject matter of algorithm design. We will simply use
an existing approach. The reader could even choose to ignore that aspect of this article entirely.
A POSSIBLE SOLUTION
Listing 1 shows a "correct"
solution. It is quite representative of average Java code that one finds today. But is it
object-oriented? Even though the code is in a class and the processing is broken up into two methods,
this code has very little binary reusability. Of course, if the
static readInt() method had been public, it could be reused. Reuse
of static methods is not useless, but it is often nothing more than the "function-library" type of
reuse from the procedural programming era. The full benefits of object-orientation are not to be
found in this manner. The code for checking primality could also have been moved into another static
method of its own, and would represent an improvement in quality (via functional decomposition),
but that is not going to make the code any more object-oriented.
DESIGNING FOR REUSABILITY
Good object-oriented design would take even a simple problem such as this and carve out reusable
pieces from it. Of course, there is an infinite number of ways in which one could carve up a piece
of code into multiple classes. The breaking-up process is guided by the needs of the situation.
In object-orientation, the key goal is reuse, and one thinks in terms of assembling applications
out of components. In the present situation, what components could we wire-up to build our application?
One approach is to think of our application as consisting of a user interface distinct from the main
functionality, namely, that of checking for primality. Thus, we would separate those two aspects
into distinct classes. Prying apart the two pieces in this manner provides the following immediate benefits:
- The prime checker can now work with different user interfaces (e.g., text and graphical).
- The user interface could be used in its own right in other problems (any problem that takes a
number as input and does something based on it).
Two reusable components would be created where there were none before.
This approach looks promising and we will take it further. In fact, it turns out to be an application
of a common idea in object-orientation—the "Model-View-Controller" (MVC) paradigm, which suggests
that we should separate the user interface (view) of any application
from its core (model). It also suggests that the model and view
should not be tightly coupled to each other, so that they can plug-and-play in different situations
on their own. Hence, the controller that mediates between them. The code we will now develop illustrates
these ideas.
Thus, our design is going to involve three classes:
- model class—which will contain a number and report on whether
it is prime or not.
- view class—which will do the work of presenting the user
interface.
- controller class—which will tie the model and view together.
DEVELOPING THE CLASSES
The model class is straightforward. It keeps a single number as an
instance variable, and whenever requested, tests it for primality. The
model class is kept completely free of any issues other than its main job.
We would like to maximize reusability of code that we develop. There are two sides to the reuse
coin—component and framework. This is best brought out by an analogy with electronic
hardware, where integrated circuits (ICs) are plugged into circuit boards. ICs are analogous to
components, and circuit boards are analogous to frameworks. Unless both are designed in a compatible
manner, we will not gain reusability of either. There is not much use in having a great IC unless
there is a suitable circuit board to plug it into. Equally useless is the most advanced circuit board
unless there are components out there that will fit into it. The same applies to object-oriented design.
When developing our model class, we would like to think of it as a
component that can be plugged into a framework. This will allow the framework to work not only with
the present model class that we are developing but also with any
new ones that we may develop in the future. Also, our model class
will be able to plug into any other compatible framework. Thus, rather than developing the
model class in isolation, let us create a Java interface
NumberProblemModel and make our model
class implement that interface. We then develop other classes solely in terms of the
NumberProblemModel interface and avoid tightly coupling them
with any specific kind of number problem. In doing this, we are following a general guideline
that recommends building applications around interfaces rather than tying them to specific
classes.1 The NumberProblemModel interface is
shown in Listing 2.
We can imagine numerous classes that might implement the same interface and do other useful things.
With this interface in place, we are now ready to implement the model
class that checks for primality. We have designed the solve() method
in the interface to return an Object because different types of
problems may return different types of results (in a more extensive implementation one might define
a separate class to describe the type of the return value from the solve()
method). The getProblemName() method makes our model self-describing.
The code of the model class, MyPrimeTester
is given in Listing 3 (Listings 3-9
available only at Java Report Online).
Developing the view is a bit more complex because we want to be able to
use our model in a text or graphical user interface (GUI). What does a view need to be able to do? If
we go simply by the code in MyFirstPrimeTester, then we might decide
that the user interface is controlled by the application; that is, we might design the
view class in such a way that it provides a number in response to an
explicit request like getNumber( ). This will pose a problem if we
want our model to be able to work with both text and GUIs. GUIs
allow the user to control application flow. In these situations, the application presents the user
interface (UI) and lets the UI take control of the flow. The UI then communicates with the rest of
the application through events. We will therefore design even our text UI to act according to the
event-based approach.
Figure 1. The flow of events using a UML sequence diagram.
In this scheme of things, the controller first registers interest
with the view and waits to be notified about events occurring at
the UI. When an event occurs, the view lets the
controller know about it by invoking a suitable method on it.
Figure 1 shows the flow of events using a UML sequence diagram (without details of method arguments).
In Figure 1, after the controller has registered interest with the
view, the user enters a number and the
view then lets the controller know
about it by invoking the numberEntered( ) method. In response, the
controller sets the model in motion
by first setting the number for the problem to be solved by invoking the
setNumber() method, and then invoking the
solve() method on the model. The
controller then asks the view to
display the result. We will delve into these details as we develop the other classes.
Notice that there is no direct communication between the view and
the model. The controller mediates
their interactions. To enable these communications to take place, the
view needs a reference to the controller,
and the controller needs a reference to the model. How do we design
these associations?
We will implement both of these associations through instance variables. What should be the type of
the instance variable in the view? Can it be an instance of the
model class? Surely not, because the
view only communicates with the
controller. If we make the view get
involved directly with the model, then it is not going to be generic
enough to be reused in other situations.
Does the view really care about the class of the objects
that are interested in its services? No; instances of any class can use a given
view. In fact, it is important not to tightly couple
the view to work with only instances of a specific
class. De-coupling of this form is precisely what makes it possible to develop plug-and-play systems.
It is thus clear that the instance variable in view that will
hold the reference to the controller should ideally not be
of any class type. We will make it an interface type called NumberListener.
Any class that wants to use the view would implement NumberListener.
This is how we allow the same view to plug-and-play with many classes.
The code for NumberListener is given
in Listing 4.
(In practice, we would want to conform to the JavaBeans specification, have our interface
extend java.util.EventListener, and also have a special event
class that extends java.util.EventObject defined for this
application. In the interest of brevity, we are avoiding those details here.)
Our view class will have an instance variable of type
NumberListener. When an event takes place (user enters a number, user
wants to quit the application) the view will notify the listener by
invoking the appropriate method on that listener.
The view and controller are
interdependent; they need to talk to each other. We have already seen that the
view needs to notify the controller of
events. However, the controller needs to also know about the
view; it has to register interest in the first place so that it can
be notified when events take place. So we go through the same set of arguments as first discussed.
What type of object does the controller think it is talking with?
It does not really care who provides it with the data. Once again we develop an
interface, NumberProblemView, which lays out the basic message
protocol of the UI. The code for NumberProblemView is shown in
Listing 5.
Based on the two interfaces NumberListener and
NumberProblemView, we are ready to build the text UI
class. Listing 6 shows the code.
Now that we have the model and view
in place, we are in a position to design the controller class, which
will mediate between the model and the
view. Once again, we will try to make the
controller generic. The code in the
controller is independent of any specific
model or view class.
The controller has a constructor that allows us to specify both
the UI to be used as well as the problem to be solved. The types of both these arguments have been
specified as the corresponding interface types (NumberProblemView
and NumberProblemModel) and not the actual classes. This way
the same controller can be used with any new classes we
choose to design. For example, we might design a GUI for number problems, and we might develop
other model classes that implement the
NumberProblemModel interface. The same
controller will work with all of them, unchanged. It also
has a main method that can be used to start up the application. In anticipation of new
view and model classes that
may be developed, we have designed the static main() method
in controller to accept two command line arguments that
supply the class names of the view and
model classes, respectively. The
main() method gets the class descriptors of these classes by
using the Class.forName() method and then creates instances by
invoking the newInstance() method on the resulting class
descriptors. The results from the two newInstance() invocations
are suitably typecast. The code for NumberProblemController is
given in Listing 7.
Note how the code in all the classes has been made very generic. They are all coded terms of interfaces
and can thus be reused in binary form with new classes that we might develop in the future.
We now present a GUI class NumberProblemGUI in
Listing 8,
and another model class MyFactorAdder in
Listing 9 (which
computes the sum of all the divisors of a given number). Both of these plug-and-play with the
existing classes, thereby demonstrating the validity of this approach to building reusable classes.
CONCLUSION
To really benefit from using Java as a language to build systems, we need to fully exploit its
object-oriented features. This is an aspect that is today receiving less attention than it
ought to. I've shown, by example, how to develop applications around interfaces, rather than
make them dependent on specific classes and mar reusability.
Inheritance is also commonly used as a mechanism to make multiple classes share a common
external interface. However, inheritance is basically a code-reuse mechanism. It should
be used only when subclasses can inherit significant amounts of code. Premature decisions
on inheritance can cause problems later on. However, if one chooses the interface route
early on, it would still be possible to opportunistically adopt inheritance wherever it
proves to be appropriate. An interface-based design enhances the extensibility of a system
by keeping open the possibility that future classes from an entirely unrelated branch of
the class hierarchy will be able to plug-and-play with the classes we have developed.
We have utilized an application of the MVC paradigm as our example. The MVC paradigm is
widely used within Smalltalk environments to decouple models and UIs. Java has also adopted
the MVC approach in its new swing components. What we
showed here is a simplified version of MVC. In a typical implementation, the model would
be capable of keeping track of multiple listeners and would notify them whenever it changed.
In our implementation the model was incapable of changing
on its own. It was spurred into action only by events happening at the UI and therefore did
not need to keep track of any listeners.
The benefits of MVC come at the price of increased complexity. Instead of the one class
in our initial design, we ended up with six (including the interfaces). This makes the
application more difficult to comprehend. There could also be efficiency implications
because of the increased number of objects involved; but that is hardly an issue in the
UI portion of an application, where the user's reaction time will easily offset any such
inefficiency. Further, the execution overhead imposed by MVC is very small. The increased
reusability and maintainability easily justify the added complexity. Environments like
VisualBasic and PowerBuilder tend to combine the view
and the controller into one entity, thereby compromising
the reusability of the view. When the
view and controller are combined,
the controller portion of code will be repeated when the
same model needs a different view.
In general, MVC has been widely accepted. The only conceivable situations when one would not pay
the price of the increased complexity would be in trivial, one-off applications, and in
applications where there is absolutely no possibility of a model
requiring more than one view.
REFERENCE
1. Gamma, E. et al., Design Patterns: Elements of Reusable Object-Oriented Software,
Addison-Wesley, 1995.
Quantity reprints of this article can be purchased by phone: 717.560.2001, ext.39 or
by email: [email protected].
|