|
Articles Index
Jim Coker, MageLang Institute
February 1997
One of the nice things about programming in Java is that the language
and its class library are designed to provide practical solutions to real
object-oriented programming problems. In the object programming
community, many of these solutions have been well documented as design
patterns; each pattern is a generic solution to a common problem. One
such pattern is known as the Observer pattern, which is a solution to
the updating problem that arises when some objects have a dependency
relationship with others. Java provides a ready-made implementation for this
pattern through the Observable class and the Observer interface.
However, one occasionally runs into design issues that require
careful thinking in order to create a solution appropriate to the
task at hand. This article discusses the use of the
Observable class, and the Observer interface, and
shows how to solve some potential problems along the way.
Introducing Observer Patterns
A common scenario for using the Observer pattern involves a subject
that has multiple views. Each view object needs to be updated whenever
the subject changes. One example is a drawing program,
where the subject is the internal representation of the drawing and
views are different windows opened on the drawing. Any time you
make a change in the drawing, each of the windows needs to be updated.
The Observer pattern provides a way for each of the views to be
notified whenever the subject has changed, without requiring that the
subject know anything about the views, other than that they are
observers. If the subject has to know more about the views, the
program can quickly become difficult to manage--the code in the
subject that handles updates becomes dependent on each of the views it
must support.
The Observer pattern solves this update problem as follows: The
Subject (the object being observed) maintains a list of its observers.
Whenever the subject makes a change to itself, it notifies all
observers that a change has been made. It might also provide some generic
change information with that notification. Each view gets the same
notification. Usually this notification takes the form of a method
call on the observer, with update information stored as a parameter to
that call. The only thing that the subject knows about an observer is
that it understands that method call.
An Observer Pattern Implementation in Java
The Java utilities package provides a ready-made implementation of the
Observer pattern with the Observable class, which implements the
updating behavior of the Subject and the Observer
interface, which contains the update method to be called by the
Observable, and can be easily implemented by any candidate observer
objects.
Here are the interfaces for Observable and Observer, with their
methods grouped according to their function:
public class java.util.Observable
{
// Constructors
public Observable();
// here are methods for maintaining a
// list of observers
public void addObserver(Observer o);
public int countObservers();
public void deleteObserver(Observer o);
public void deleteObservers();
// methods for maintaining a changed flag
protected void clearChanged();
protected void setChanged();
public boolean hasChanged();
// methods for notifying observers of a change
public void notifyObservers();
public void notifyObservers(Object arg);
}
public interface java.util.Observer
{
/** this method will be called by an observable
class whenever it wants to notify this observer
of a change. The second argument can be used
to pass information about the change to the
observer */
public abstract void update(Observable o, Object arg);
}
|
A Simplest Possible Example
Here is a very small example showing how the Observer pattern can
be implemented using Observable and Observer.
First create a Subject class with a simple data value that inherits from Observable.
public class Subject extends Observable {
private String value;
public void setValue(String s) {
value = s;
setChanged(); // set changed flag
notifyObservers(value); // do notification
}
}
|
When the Subject is changed through the setValue method two
calls need to be made to ensure notification of all observers, one to set
the changed flag, the other to notify observers. If the changed flag
is not set, the notifyObservers call will do nothing. When
notifyObservers is called, all observer objects will have
their update method called. The Subject has no detailed
information on the observers at all; it only has to make these two
calls when it changes.
Next a View class is created that accepts notifications
whenever its Subject is updated. For this example, the View
class just prints out a message.
class View implements Observer {
public void update(Observable o, Object str) {
System.out.println("update: " + str.toString());
}
}
|
Finally, you need to create a Subject and
View, and link them together. This main method can be inserted
as a method in the Subject class in order to run
it as a standalone application.
public static void main(String[] args) {
Subject s = new Subject();
View v = new View();
s.addObserver(v);
// calling setValue on the subject
// will trigger an update call to the view
s.setValue("test value");
}
|
A More Realistic Example
The above example shows an Observer pattern implementation using
Observable and Observer that is small enough to get
an idea of how the pattern works, but is too small for one to run into
any real-world problems, or get a good understanding of how powerful
the Observer pattern is.
The next example shows an implementation
that requires some problem solving to reach a solution. It involves a
counter with two views, a textual view, and a scrollbar view. The
counter is a GUI widget comprising a label, a button, and an
integer value. Whenever the button is pushed, the integer value is
incremented and the label is updated. Both views will also be
updated.
Here is an early design of the Counter, one that does not
expect to be viewed by other objects:
class Counter extends Panel {
Label countLabel;
Button incButton;
private int _count = 0;
/** create a new counter with a Label
and an increment Button */
public Counter() {
setLayout(new BorderLayout());
add("Center",countLabel = new Label("Count: 0"));
add("South",incButton = new Button("Increment"));
}
/** return counter value */
public int value() {
return _count;
}
/** handle button push */
public boolean action(Event evt, Object arg) {
...
}
}
|
To make the Counter observable, this example has two
observers, a textual view, and a scroll view. The obvious way to
achieve that behavior is to have Counter inherit from Observable.
However, the counter shown already inherits from the AWT class Panel,
which investigation reveals that it must do. Otherwise, due to
type restrictions, it would not be able to properly fit into a component
hierarchy. Defining the Counter so that it satisfies a component
interface is not an option, as AWT does not define an interface
(only classes) for GUI components, and Java does not allow an
interface to extend a class. So a way must be found to add Observable
behavior to the counter without using inheritance.
A reasonable question to ask at this point is: "Why not make the
Counter object a non-GUI object (so it can inherit from Observable),
and have the label and pushbutton part of the original Counter as
one of the views?" Well, even though this Counter object could easily
be reconfigured in that way, there are many cases where inheritance
cannot be shifted around. One likely case is that the subject is already provided and can be subclassed, but cannot have its
inheritance modified. Or the subject may be part of a complex data
structure (such as an abstract syntax tree) where, as with AWT
components, inheritance is used to define a hierarchy for organizing
objects.
This example shows a way to use the Observable object without
having to inherit from it by delegating the behavior that Counter
needs to a contained Observable object. Access to the
Observable object is provided through an accessor method so
that other objects can add observers. Here are the code additions to
the Counter class:
class Counter extends Panel {
...
Observable _observable; // new data member
public Counter() {
...
// initialize observable in constructor
_observable = new MyObservable();
}
// new method to get access to observable
public Observable observable() {
return _observable;
}
|
However, in the process of implementing the delegation approach,
another problem pops up. Two important methods of Observable
are protected: setChanged and clearChanged; the
intention being that only the observed subject should have direct
control over this flag. But with the delegation approach shown above,
the Counter will not be able to access the changed flags. To
solve this problem, a new subclass of Observable is created
to open up its interface and make those two methods visible publicly.
class MyObservable extends Observable {
public void clearChanged() {
super.clearChanged();
}
public void setChanged() {
super.setChanged();
}
}
|
Now the Counter class can contain an Observable object (an instance of
MyObservable) and access these methods. The change flag provided by
the Observable object is still protected from users other than the
Counter, as the Counter's observable method returns an object of class
Observable, in which those methods are protected.
The New JDK 1.1 Event Model
By the way, the observer/observable change notification structure is important for reasons beyond the scope of this article.
The new event model for the upcoming JDK 1.1 is based on the same
relationship between objects that change state and objects that are
notified about state change. This new event model is referred to as
the delegation-based event model (or delegation event
model, for short).
The delegation model relies on event source objects and
event listener objects. Objects that want to be
informed about state changes in AWT components register themselves
with event source objects by supplying an event handler or listener
object to the source. This registration occurs by calling a listener
registration method in the source with the listener object supplied as
an argument. The source maintains a collection of all registered
listener objects. When a state change occurs in the source, each
listener object is notified by the source object calling a method with
a predetermined name and signature in the listener object.
In the new AWT event model, event sources are like the observable
objects presented here; listener objects play a role similar to
observer objects.
For more information on the new AWT delegation-based event model, read
the section titled "Java AWT: Delegation Event Model" in "
JDK1.1 AWT Enhancements."
Back to the Regularly Scheduled Solution
Here is the code for a textual view of the counter. It implements the
Observable interface by providing the necessary update method. For
this example, the second argument to the update method is expected to
be the counter itself. Whenever update is called, the value of the
counter is retrieved and the text field updated. It is up to the
programmer to determine how the second argument is used to pass
change information, but it should be the same for all views, and
clearly documented in all views as well as the subject.
class TextView extends TextField implements
Observer {
public TextView(Counter c) {
super(10);
setEditable(false);
setText(String.valueOf(c.value()));
}
/** update method called by observed Counter,
the second argument is the Counter object */
public void update(Observable o, Object counter) {
setText(String.valueOf(((Counter)counter).value()));
}
}
|
The ScrollView object is very similar to the TextView, it just replaces
the TextField object with a Scrollbar object.
class ScrollView extends Scrollbar implements
Observer {
/** create a horizontal scrollbar
with a range from 0 to 10 */
public ScrollView(Counter c) {
super(Scrollbar.HORIZONTAL,0,1,0,10);
setValue(c.value());
}
/** update method called by observed Counter,
the second argument is the Counter object */
public void update(Observable o, Object counter) {
setValue(((Counter)counter).value());
}
}
|
Now to wrap things up with a complete listing of the Counter class, as
well as an Applet class, to demonstrate how to link the observers to
the counter they observe. There is a link at the end of the article
to the complete source file. Note also how the Counter method for event
handling notifies the Counter's observers as soon as its value is
changed.
/** This is a counter class that delegates
observable behavior to a contained MyObservable
object. A method is provided to access this
object. The counter object has a private integer
variable to hold the counter value, a label
to display it, and a button to increment the value.
Note that since this Counter is a GUI widget,
it inherits its behavior from the AWT class Panel.
Since you also want it to be observable, that
behavior must be provided through delegation. */
class Counter extends Panel {
Label countLabel;
Button incButton;
private int _count = 0;
MyObservable _observable;
public Counter() {
setLayout(new BorderLayout());
add("Center",countLabel = new Label("Count: 0");
add("South",incButton = new Button("Increment"));
_observable = new MyObservable();
}
public int value() {
return _count;
}
/** make observable object accessible */
public Observable observable() {
return _observable;
}
/** handle clicks on the button,
notify observers of change */
public boolean action(Event evt, Object arg) {
if(evt.target == incButton) {
_count++;
countLabel.setText(String.valueOf(_count));
_observable.setChanged();
_observable.notifyObservers(this);
return true;
}
return false;
}
}
/** A simple applet to show the Counter and its two
Observers, a TextView and a ScrollView */
public class ObserverTest extends Applet {
Counter c;
TextView tv;
ScrollView sv;
public ObserverTest() {
setLayout(new BorderLayout());
add("North",c = new Counter());
add("Center",tv = new TextView(c));
add("South",sv = new ScrollView(c));
/* link the TextView observer to the
Counter observable */
c.observable().addObserver(tv);
// do same for the ScrollView object
c.observable().addObserver(sv);
}
}
|
Looking to Scalability in the Real World
Using the Observer pattern helps to keep subjects in touch with
their viewers, but what happens when a subject becomes complex, and
the views have very different interests with regard to the subject?
Consider an airline ticket reservation system, where the subject is a
ticket object. One of many views might be a billing view transmitted
to a credit card company, and another might be a seating view used to
determine the all-important window or aisle allocation. If there is a
change in seating, the ticket object changes, but the billing view
does not need to be updated. This is an example of how the use of the
Observer pattern can become complicated by real-world issues. The
trick is to find solutions that retain the character of the original
solution, and not to introduce more problems than they solve.
One approach to the airline ticket problem is the use of aspects, as
described in Design Patterns, by Gamma, Helm, Johnson, and Vlissides. Whenever an observer registers with a subject,
it is done with respect to an aspect of that subject. Then, whenever
the subject determines that an aspect of itself has changed, it
notifies only those observers that are interested in that change. This
makes coding the update methods easier, as they each have fewer
changes to deal with, and makes the program more efficient, as only
necessary update methods are called. For the ticket scenario above,
the two aspects needed could be billing and seating. The
addObserver method would take this into account:
//declaration of addObserver and a couple of aspects
public synchronized void addObserver(
Observer o, int aspect);
public final static int BILLING = 1;
public final static int SEATING = 2;
// an example call to the new addObserver method
aTicket.addObserver(
aBillingView,AirlineTicket.BILLING);
|
Implementing this code requires some significant
extensions to the Observable class, so you might like to complete that exercise in the privacy of your own home!
References
Arnold, Ken, and James Gosling. The Java Programming Language,
Addison-Wesley, 1996.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides.
Design Patterns, Addison-Wesley, 1994.
Source Code
|
|