Bruce Eckel’s Thinking in Java | Contents | Prev | Next |
observer
pattern solves a fairly common problem: What if a group of objects needs to
update themselves when some object changes state? This can be seen in the
“model-view” aspect of Smalltalk’s MVC
(model-view-controller), or the almost-equivalent “Document-View
Architecture.” Suppose that you have some data (the
“document”) and more than one view, say a plot and a textual view.
When you change the data, the two views must know to update themselves, and
that’s what the observer facilitates. It’s a common enough problem
that its solution has been made a part of the standard
java.util
library.
are two types of objects used to implement the observer pattern in Java. The Observable
class keeps track of everybody who wants to be informed when a change happens,
whether the “state” has changed or not. When someone says
“OK, everybody should check and potentially update themselves,” the
Observable
class performs this task by calling the notifyObservers( )
method for each one on the list. The
notifyObservers( )
method is part of the base class
Observable.
are actually two “things that change” in the observer pattern: the
quantity of observing objects and the way an update occurs. That is, the
observer pattern allows you to modify both of these without affecting the
surrounding code.
following example is similar to the
ColorBoxes
example from Chapter 14. Boxes are placed in a grid on the screen and each one
is initialized to a random color. In addition, each box
implements
the Observer
interface and is registered with an
Observable
object. When you click on a box, all of the other boxes are notified that a
change has been made because the
Observable
object automatically calls each
Observer
object’s
update( )
method. Inside this method, the box checks to see if it’s adjacent to the
one that was clicked, and if so it changes its color to match the clicked box.
//: BoxObserver.java // Demonstration of Observer pattern using // Java's built-in observer classes. import java.awt.*; import java.awt.event.*; import java.util.*; // You must inherit a new type of Observable: class BoxObservable extends Observable { public void notifyObservers(Object b) { // Otherwise it won't propagate changes: setChanged(); super.notifyObservers(b); } } public class BoxObserver extends Frame { Observable notifier = new BoxObservable(); public BoxObserver(int grid) { setTitle("Demonstrates Observer pattern"); setLayout(new GridLayout(grid, grid)); for(int x = 0; x < grid; x++) for(int y = 0; y < grid; y++) add(new OCBox(x, y, notifier)); } public static void main(String[] args) { int grid = 8; if(args.length > 0) grid = Integer.parseInt(args[0]); Frame f = new BoxObserver(grid); f.setSize(500, 400); f.setVisible(true); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } class OCBox extends Canvas implements Observer { Observable notifier; int x, y; // Locations in grid Color cColor = newColor(); static final Color[] colors = { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow }; static final Color newColor() { return colors[ (int)(Math.random() * colors.length) ]; } OCBox(int x, int y, Observable notifier) { this.x = x; this.y = y; notifier.addObserver(this); this.notifier = notifier; addMouseListener(new ML()); } public void paint(Graphics g) { g.setColor(cColor); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { notifier.notifyObservers(OCBox.this); } } public void update(Observable o, Object arg) { OCBox clicked = (OCBox)arg; if(nextTo(clicked)) { cColor = clicked.cColor; repaint(); } } private final boolean nextTo(OCBox b) { return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1; } } ///:~
you first look at the online documentation for
Observable,
it’s a bit confusing because it appears that you can use an ordinary
Observable
object to manage the updates. But this doesn’t work; try it – inside
BoxObserver,
create an
Observable
object instead of a
BoxObservable
object and see what happens: nothing. To get an effect, you
must
inherit from
Observable
and somewhere in your derived-class code call setChanged( ).
This is the method that sets the “changed” flag, which means that
when you call notifyObservers( )
all of the observers will, in fact, get notified. In the example above
setChanged( )
is simply called within
notifyObservers( ),
but you could use any criterion you want to decide when to call
setChanged( ).
contains a single
Observable
object
called
notifier,
and every time an
OCBox
object is created, it is tied to
notifier.
In
OCBox,
whenever you click the mouse the
notifyObservers( )
method is called, passing the clicked object in as an argument so that all the
boxes receiving the message (in their
update( )
method)
know who was clicked and can decide whether to change themselves or not. Using
a combination of code in
notifyObservers( )
and
update( )
you can work out some fairly complex schemes.
might appear that the way the observers are notified must be frozen at compile
time in the
notifyObservers( )
method. However, if you look more closely at the code above you’ll see
that the only place in
BoxObserver
or
OCBox
where you’re aware that you’re working with a
BoxObservable
is at the point of creation of the
Observable
object
– from then on everything uses the basic
Observable
interface. This means that you could inherit other
Observable
classes and swap them at run-time if you want to change notification behavior
then.