Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
The Observer pattern solves a fairly common problem: what if
a group of objects needs to update themselves when some other 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 two views: a plot view
and a textual view. When you change the data, the views must be told to update
themselves, and that s what the observer facilitates.
Two types of objects are used to implement the observer
pattern in the following code. The Observable class keeps track of the
objects that want to be informed when a change happens. The Observable
class calls the notifyObservers( ) member function for each
observer on the list. The notifyObservers( ) member function is
part of the base class Observable.
There are 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.
You can implement the observer pattern in a number of ways,
but the code shown here will create a framework from which you can build your
own observer code, by following the example. First, this interface describes
what an observer looks like:
//: C10:Observer.h
// The Observer interface.
#ifndef OBSERVER_H
#define OBSERVER_H
class Observable;
class Argument {};
class Observer {
public:
// Called by the observed object, whenever
// the observed object is changed:
virtual void update(Observable* o, Argument* arg) =
0;
virtual ~Observer() {}
};
#endif // OBSERVER_H ///:~
Since Observer interacts with Observable in
this approach, Observable must be declared first. In addition, the Argument
class is empty and only acts as a base class for any type of argument you want
to pass during an update. If you want, you can simply pass the extra argument
as a void*. You ll have to downcast in either case.
The Observer type 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 it s 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 will be of type Observable:
//: C10:Observable.h
// The Observable class.
#ifndef OBSERVABLE_H
#define OBSERVABLE_H
#include <set>
#include "Observer.h"
class Observable {
bool changed;
std::set<Observer*> observers;
protected:
virtual void setChanged() { changed = true; }
virtual void clearChanged() { changed = false; }
public:
virtual void addObserver(Observer& o) {
observers.insert(&o);
}
virtual void deleteObserver(Observer& o) {
observers.erase(&o);
}
virtual void deleteObservers() {
observers.clear();
}
virtual int countObservers() {
return observers.size();
}
virtual bool hasChanged() { return changed; }
// If this object has changed, notify all
// of its observers:
virtual void notifyObservers(Argument* arg = 0) {
if(!hasChanged()) return;
clearChanged(); // Not "changed" anymore
std::set<Observer*>::iterator it;
for(it = observers.begin();it != observers.end(); it++)
(*it)->update(this, arg);
}
virtual ~Observable() {}
};
#endif // OBSERVABLE_H ///:~
Again, the design here is more elaborate than is necessary. As
long as there s a way to register an Observer with an Observable
and a way for the Observable to update its Observers, the set of
member functions doesn t matter. However, this design is intended to be
reusable. (It was lifted from the design used in the Java standard library.)
The Observable object 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. 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.
The collection of Observer objects is kept in a set<Observer*>
to prevent duplicates; the set insert( ), erase( ), clear( ),
and size( ) functions are exposed to allow Observers to be
added and removed at any time, thus providing runtime flexibility.
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 that 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. It then 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 any effect, you must
derive 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 the observers will, in fact, get
notified. Where you call setChanged( ) depends on the logic
of your program.
Now we encounter a dilemma. Objects that are being observed
may have more than one such item of interest. For example, if you re dealing
with a GUI item a button, say the items of interest might be the mouse clicked
the button, the mouse moved over the button, and (for some reason) the button
changed its color. So we d like to be able to report all these events to
different observers, each of which is interested in a different type of event.
The problem is that we would normally reach for multiple
inheritance in such a situation: I ll inherit from Observable to deal
with mouse clicks, and I ll er inherit from Observable to deal with
mouse-overs, and, well, hmm, that doesn t work.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |