This is a local copy of a Javaworld page by Todd Sunsted. The only thing changed in the page is the commenting out of various icons (including figures) and a link to driver code for the applet.
Summary
Here's an introduction to the Observer interface and the Observable class found in the Java programming language class library. This article describes both and demonstrates how they can be used to implement programs based on the Model/View/Controller architecture introduced and popularized by the Smalltalk programming language. (2,200 words)
Here's the problem: You're designing a program that will render data describing a three-dimensional scene in two dimensions. The program must be modular and must permit multiple, simultaneous views of the same scene. Each view must be able to display the scene from a different vantage point, under different lighting conditions. More importantly, if any portion of the underlying scene changes, the views must update themselves.
None of these requirements presents an insurmountable programming challenge. If the code that handles each requirement had to be written de novo, however, it would add significant work to the overall effort. Fortunately, support for these tasks is already provided by the Java class library in the form of interface Observer and class Observable. The functionalities of these two were inspired, in part, by the requirements of the Model/View/Controller architecture.
The Model/View/Controller
architecture
The Model/View/Controller architecture was introduced as part of the
Smalltalk-80 version of the Smalltalk programming language (a popular
object-oriented programming language invented by Alan Kay). The
Model/View/Controller architecture was designed to reduce the
programming effort required to build systems that made use of
multiple, synchronized presentations of the same data. Its central
characteristics are that the model, the controllers, and the views are
treated as separate entities, and that changes made to the model should be
reflected automatically in each of the views.
In addition to the program example described in the opening paragraph above, the Model/View/Controller architecture may be used for projects such as the following:
The Model/View/Controller architecture has several benefits:
Defining the parts
The model is the object that represents the data in the
program. It manages the data and conducts all transformations
on that data. The model has no specific knowledge of either its
controllers or its views -- it contains no internal references to
either. Rather, the system itself takes on the responsibility of
maintaining links between the model and its views and notifying the
views when the model changes.
The view is the object that manages the visual display of the data represented by the model. It produces the visual representation of the model object and displays the data to the user. It interacts with the model via a reference to the model object itself.
The controller is the object that provides the means for user interaction with the data represented by the model. It provides the means by which changes are made, either to the information in the model or to the appearance of the view. It interacts with the model via a reference to the model object itself.
At this point a concrete example might be helpful. Consider as an example the system described in the introduction.
The central piece of the system is the model of the three-dimensional scene. The model is a mathematical description of the vertices and the faces that make up the scene. The data describing each vertex or face can be modified (perhaps as the result of user input or a scene distortion or morphing algorithm). However, there is no notion of point of view, method of display (wireframe or solid), perspective, or light source. The model is a pure representation of the elements that make up the scene.
The portion of the program that transforms the data in the model into a graphical display is the view. The view embodies the actual display of the scene. It is the graphical representation of the scene from a particular point of view, under particular lighting conditions.
The controller knows what can be done to the model, and implements the user interface that allows that action to be initiated. In this example, a data entry control panel might allow the user to add, modify, or delete vertices and faces.
Observer and Observable
The Java programming language provides support for the
Model/View/Controller architecture with two classes:
These two classes can be used to implement much more than just the Model/View/Controller architecture. They are suitable for any system wherein objects need to be automatically notified of changes that occur in other objects.
Typically, the model is a subtype of Observable and the view is a subtype of observer. These two classes handle the automatic notification function of the Model/View/Controller architecture. They provide the mechanism by which the views can be automatically notified of changes in the model. Object references to the model in both the controller and the view allow access to data in the model.
Observer and Observable functions
The following are code listings for observer and observable functions:
Observer
public void update(Observable obs, Object obj)
Observable
public void addObserver(Observer obs)
public void deleteObserver(Observer obs)
public void deleteObservers()
public int countObservers()
protected void setChanged()
protected void clearChanged()
public boolean hasChanged()
public void notifyObservers()
public void notifyObservers(Object obj)
notify()
method of the observer.
How to use interface Observer and class Observable
The following section describes in detail how to create a new
observable class and a new observer class, and how to tie the two
together.
Extend an observable
A new class of observable objects is created by extending class
Observable. Because class Observable already implements all of the
methods necessary to provide the observer/observable behavior, the
derived class need only provide some mechanism for adjusting and
accessing the internal state of the observable object.
In the class ObservableValue listing below, the internal state of the model is
captured by the integer n
. This value is accessed (and,
more importantly, modified) only through public accessors. If the
value is changed, the observable object invokes its own
setChanged()
method to indicate that the state of the
model has changed. It then invokes its own
notifyObservers()
method in order to update all of the
registered observers.
import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } }Implement an observer
update()
method be
provided in the new class. The update()
method is called
whenever the observable changes state and announces this fact by
calling its notifyObservers()
method. The observer
should then interrogate the observable object to determine its new
state, and, in the case of the Model/View/Controller architecture,
adjust its view appropriately.
In the following class TextObserver listing, the notify()
method first checks to ensure that the observable that has announced an
update is the observable that this observer is observing. If it is, it
then reads the observable's state, and prints the new value.
import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } }Tie the two together
addObserver()
method. The addObserver()
method adds the observer to the internal list of observers that should
be notified if the state of the observable changes.
The example below, showing class Main, demonstrates how to use the
addObserver()
method to add an instance of the
TextObserver class (see the TextObserver listing above) to the
observable list maintained by the ObservableValue class (see the
ObservableValue listing above).
public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } }How it all works together
setValue()
in the example above.
setChanged()
method to indicate that its state has changed. It then calls
notifyObservers()
to notify the observers that it has changed. The call to notifyObservers()
could
also be performed elsewhere, such as in an update loop running in
another thread.
update()
methods on each of the observers are
called, indicating that a change in state has occurred. The observers
access the model's data via the model's public accessor methods and
update their respective views.
Like the model in the class ObservableValue listing shown previously, the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in class ObservableValue. The code for the model is found here.
Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here. Instances of this view can be created by pressing the upper button in Figure 3.
By designing the system using the Model/View/Controller architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here. Instances of this view object can be created by pressing the bottom button in Figure 3.
U.B.C. Addition. The code for the applet to create and run this example is here and here.
Conclusion
Coming up next month is an introductory "how-to" on graphics that
should prepare the way for future columns on advanced topics such as
animation and custom components. As always, I am interested in
hearing from you. Send me mail if you have a suggestion for a future
column or a question you would like addressed.
About the author
Todd Sundsted has been writing programs since computers became
available in desktop models. Though originally interested in building
distributed object applications in C++, Todd moved to the Java
programming language when Java became the obvious choice for that sort
of thing. In addition to writing, Todd provides Internet and Web
consulting services to companies in the southeastern United States.
Resources