10: Callbacks
Decoupling code behavior
Observer, and a category of
callbacks called “multiple dispatching (not in Design
Patterns)” including the Visitor from Design
Patterns.
Observer
Like the other forms of callback, this
contains a hook point where you can change code. The difference is in the
observer’s completely dynamic nature. It is often used for the specific
case of changes based on other object’s change of state, but is also the
basis of event management. Anytime you want to decouple the source of the call
from the called code in a completely dynamic way.
The
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.
There 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.
There 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.
-------------
Observer is an
“interface” class that only has one member function,
update( ). This function is called by the object that’s being
observed, when that object decides its time to update all its observers. The
arguments are optional; you could have an update( ) with no
arguments and that would still fit the observer pattern; however this is more
general—it allows the observed object to pass the object that caused the
update (since an Observer may be registered with more than one observed
object) and any extra information if that’s helpful, rather than forcing
the Observer object to hunt around to see who is updating and to fetch
any other information it needs.
The “observed object” that
decides when and how to do the updating will be called the
Observable.
Observable has a flag to indicate
whether it’s been changed. In a simpler design, there would be no flag; if
something happened, everyone would be notified. The flag allows you to wait, and
only notify the Observers when you decide the time is right. Notice,
however, that the control of the flag’s state is protected, so that
only an inheritor can decide what constitutes a change, and not the end user of
the resulting derived Observer class.
Most of the work is done in
notifyObservers( ). If the changed flag has not been set,
this does nothing. Otherwise, it first clears the changed flag so
repeated calls to notifyObservers( ) won’t waste time. This is
done before notifying the observers in case the calls to update( )
do anything that causes a change back to this Observable object. Then it
moves through the set and calls back to the update( ) member
function of each Observer.
At first it may appear that you can use
an ordinary Observable object to manage the updates. But this
doesn’t work; to get an effect, you must inherit from
Observable and somewhere in your derived-class code call
setChanged( ). This is the member function
that sets the “changed” flag, which means that when you call
notifyObservers( ) all of the observers will,
in fact, get notified. Where you call setChanged( ) depends
on the logic of your program.
Observing flowers
Here is an example of the observer
pattern:
//: c10:ObservedFlower.java
// Demonstration of "observer" pattern.
import java.util.*;
import com.bruceeckel.test.*;
class Flower {
private boolean isOpen;
private OpenNotifier oNotify =
new OpenNotifier();
private CloseNotifier cNotify =
new CloseNotifier();
public Flower() { isOpen = false; }
public void open() { // Opens its petals
isOpen = true;
oNotify.notifyObservers();
cNotify.open();
}
public void close() { // Closes its petals
isOpen = false;
cNotify.notifyObservers();
oNotify.close();
}
public Observable opening() { return oNotify; }
public Observable closing() { return cNotify; }
private class OpenNotifier extends Observable {
private boolean alreadyOpen = false;
public void notifyObservers() {
if(isOpen && !alreadyOpen) {
setChanged();
super.notifyObservers();
alreadyOpen = true;
}
}
public void close() { alreadyOpen = false; }
}
private class CloseNotifier extends Observable{
private boolean alreadyClosed = false;
public void notifyObservers() {
if(!isOpen && !alreadyClosed) {
setChanged();
super.notifyObservers();
alreadyClosed = true;
}
}
public void open() { alreadyClosed = false; }
}
}
class Bee {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Bee(String nm) { name = nm; }
// An inner class for observing openings:
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s breakfast time!");
}
}
// Another inner class for closings:
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
class Hummingbird {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Hummingbird(String nm) { name = nm; }
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s breakfast time!");
}
}
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
public class ObservedFlower extends UnitTest {
Flower f = new Flower();
Bee
ba = new Bee("A"),
bb = new Bee("B");
Hummingbird
ha = new Hummingbird("A"),
hb = new Hummingbird("B");
public void test() {
f.opening().addObserver(ha.openObserver());
f.opening().addObserver(hb.openObserver());
f.opening().addObserver(ba.openObserver());
f.opening().addObserver(bb.openObserver());
f.closing().addObserver(ha.closeObserver());
f.closing().addObserver(hb.closeObserver());
f.closing().addObserver(ba.closeObserver());
f.closing().addObserver(bb.closeObserver());
// Hummingbird B decides to sleep in:
f.opening().deleteObserver(
hb.openObserver());
// A change that interests observers:
f.open();
f.open(); // It's already open, no change.
// Bee A doesn't want to go to bed:
f.closing().deleteObserver(
ba.closeObserver());
f.close();
f.close(); // It's already closed; no change
f.opening().deleteObservers();
f.open();
f.close();
}
public static void main(String args[]) {
new ObservedFlower().test();
}
} ///:~
The events of interest are
that a Flower can open or close. Because of the use of the inner class
idiom, both these events can be separately observable phenomena.
OpenNotifier and CloseNotifier both inherit Observable, so
they have access to setChanged( ) and can be handed to anything that
needs an Observable.
The inner class idiom also comes in handy
to define more than one kind of Observer, in Bee and
Hummingbird, since both those classes may want to independently observe
Flower openings and closings. Notice how the inner class idiom provides
something that has most of the benefits of inheritance (the ability to access
the private data in the outer class, for example) without the same
restrictions.
In main( ), you can see one
of the prime benefits of the observer pattern: the ability to change behavior at
run time by dynamically registering and un-registering Observers with
Observables.
If you study the code above you’ll
see that OpenNotifier and CloseNotifier use the basic
Observable interface. This means that you could inherit other completely
different Observer classes; the only connection the Observers have
with Flowers is the Observer
interface.
A visual example of observers
The following example is similar to the
ColorBoxes example from Chapter 14 in Thinking in Java, 2nd
Edition. 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.
//: c10:BoxObserver.java
// Demonstration of Observer pattern using
// Java's built-in observer classes.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
// 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 JFrame {
Observable notifier = new BoxObservable();
public BoxObserver(int grid) {
setTitle("Demonstrates Observer pattern");
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for(int x = 0; x < grid; x++)
for(int y = 0; y < grid; y++)
cp.add(new OCBox(x, y, notifier));
}
public static void main(String[] args) {
int grid = 8;
if(args.length > 0)
grid = Integer.parseInt(args[0]);
JFrame f = new BoxObserver(grid);
f.setSize(500, 400);
f.setVisible(true);
// JDK 1.3:
f.setDefaultCloseOperation(EXIT_ON_CLOSE);
// Add a WindowAdapter if you have JDK 1.2
}
}
class OCBox extends JPanel 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 paintComponent(Graphics g) {
super.paintComponent(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;
}
} ///:~
When 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( ).
BoxObserver 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.
It 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.
Exercises
- Create a minimal
Observer-Observable design in two classes. Just create the bare minimum in the
two classes, then demonstrate your design by creating one Observable and
many Observers, and cause the Observable to update the
Observers.
- Modify
BoxObserver.java to turn it into a simple game. If any of the squares
surrounding the one you clicked is part of a contiguous patch of the same color,
then all the squares in that patch are changed to the color you clicked on. You
can configure the game for competition between players or to keep track of the
number of clicks that a single player uses to turn the field into a single
color. You may also want to restrict a player's color to the first one that was
chosen.