Richard is a consultant with DMR Group, an international information-technology consulting firm. He can be reached at Richard.Helm@dmr.ca. Erich is a software engineer with Taligent Inc. He can be reached at Erich_ Gamma@Taligent.com. Erich and Richard are coauthors of the award-winning book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
Partitioning a system into objects is a key activity during object-oriented design. As a result of this partitioning, we may create objects that depend on other objects. Changes in one object must be reflected into others. There are many different ways to ensure that these dependencies are maintained.
For example, consider the timer object in Figure 1, which keeps the current time, and a digital-display object that shows the current time. Whenever the timer ticks, this time-display object has to be updated. In other words, the time-display object has to maintain the constraint to always reflect the timer's current time. A simple solution is to connect the timer object directly to the time-display object. Whenever the timer changes, it explicitly tells the display object to update itself. Figure 2 shows the corresponding class diagram for directly coupling the timer with its observer. Listing One is one way to implement this in C++. While this direct coupling of two objects is simple to implement, it can also introduce problems in different areas:
The intent of the Observer pattern is to define dependency relationships between objects so that when one changes, its dependents are notified and can update themselves accordingly. The Observer pattern enables objects to observe and stay synchronized with another object without coupling the observed object with its observers. The pattern has two participants: 1. a subject, and 2. the subject's dependent observers. Each time the subject changes, it is responsible for notifying its observers that it changed. Observers must ensure that whenever they are notified, they in turn make themselves consistent with their subject. A subject needs an interface that allows observers to subscribe and register their interest in changes to the subject.
The subject usually maintains a list of subscribed observers. Figure 3 illustrates these class relationships in OMT notation. Notice that we introduced two new base classes. The Subject class defines the mechanism for registering and notifying observers and the Observer class defines the update interface. This diagram illustrates how the Observer pattern breaks the direct coupling between Subject and Observer. The Subject knows nothing about its Observers except that they can be sent Update requests. This is because the reference from Subject points to the abstract class Observer. For this reason, we refer to this kind of coupling as "abstract." The abstract coupling between Subject and Observer resolves the problems mentioned previously:
The Subject and Observer classes (Listings Two and Three, respectively) illustrate how you could implement Observer in the context of the timer example. The key point about Timer is that its Tick member function calls Notify, which will call update on all its Observers.
Listing Four presents the classes Observer and DigitalTimeDisplay. DigitalTimeDisplay maintains a reference to the timer. Whenever the Timer ticks, it calls Notify, which in turn calls Update on its attached Observers. In this case, DigitalTimeDisplay receives the Update request, reads the time from the timer, and displays the time.
Notice how the Timer has no knowledge of how it is displayed. In fact, you could add another timer, say an AnalogTimeDisplay, and it would also be updated whenever the Timer ticked.
One possible simplification of the canonical observer-class structure is to absorb the Subject and Observer classes into existing classes. For example, the Microsoft Foundation Classes (MFC) use this kind of simplification. MFC supports multiple views observing a document (see "Adding Auxiliary Views for Windows Apps," by Robert Rosenberg, Dr. Dobb's Sourcebook of Windows Programming, March/April 1995). In MFC, the subject functionality is absorbed into Document, and Observer is absorbed into the View class. This solution is simpler since it requires fewer classes, but the dependency relationships can only be defined between instances of Document and Views.
There are also several variations in how inheritance is used to implement the Observer pattern. As Figure 2 shows, the interfaces for notifying and observing are defined by two classes. The Observer base class defines an interface consisting of an Update operation. In a language supporting multiple inheritance, Observer is often not a primary base class (that is, it is mixed in as an auxiliary base class). For example, in the timer example, AnalogTimeDisplay might need to inherit from a graphical base class like View. In this case, AnalogTimeDisplay mixes in the Observer class as an auxiliary class. Another variation is not to separate the notifying and observing interfaces into two separate classes. For example, in the Smalltalk-80 implementation of Observer, these two interfaces are supported by the universal Object class. Thus, each object in the system can act as both subject and observer. This is particularly convenient in a language that does not support multiple inheritance or in class libraries that don't want to rely on it. Using separate classes for subject and observer would require you to inherit from both subject and observer when an object needs to act as both.
In Figure 3, the Timer subclass inherits the Subject interface without any overriding. This is not always the case. For example, it is possible that a Subject subclass wants to customize how observers are maintained. In Smalltalk-80 the Subject base class (Object) implements the subject interface in a space-efficient way. Instead of storing the list of observers in each subject, the subject/observer mapping is maintained in a central dictionary. Only subjects that actually have observers are stored in the dictionary and have to pay for the subject service. However, this approach trades space for time: Accessing a subject's observers requires a dictionary look-up. For subjects that often notify observers, eliminate this inefficiency by storing the observers directly in an instance variable. The subject interface can then be implemented by accessing this list directly. In Smalltalk-80, this kind of Subject implementation is provided by the Object subclass Model. Consequently, the client has the choice between subject implementations with different trade-offs by inheriting from either Object or Model. As an aside, the Subject interface is an example of so-called "coupled overrides." If you override one of the subject operations, you should also override the others.
In the timer example, the Timer makes no assumptions about what objects are observing it. Instead it relies on the various timer displays querying it to retrieve the current time. The observers "pull" the state of the subject to them. An alternative is for the timer to send, or "push," the time to its observers whenever it updates them. Pushing the time requires extending the interface of Observers to accept the time in seconds. To do this, you replace the Observer class with a TimerObserver class; see Listing Six. The TimerSubject class would now have to maintain a List of TimerObservers and its Notify function would look like Listing Seven. The observers are now more tightly coupled to the timer, but they no longer need to query the timer for the time. It is still possible to have arbitrary Timer observers by subclassing from TimerObserver. However, TimerSubject and TimerObservers can no longer be used to maintain general dependency relationships.
The decision to use push or pull update protocols depends on many trade-offs: the amount of data being pushed and the expense of pushing it, the difficulty of determining what changed in the subject, the cost of notification and subsequent updates (whether subjects and observers are in the same address space), and dependencies introduced by observers being dependent on the pushed data.
The push model is more appropriate when editing text. Consider an implementation where a TextSubject stores the textual data and a TextView acting as its observer presents the text in window. When the user changes the text by entering a character, the pull model requires that the TextView completely reformat the text and refresh the window or that it somehow can determine which range of characters really changed. Both of these operations can be quite time consuming.
A more satisfactory approach is for the TextSubject to provide a "hint" of its changed text. The TextView uses this hint to update itself more efficiently. Hints can be simple, enumerated constants that provide general indications of what changed in the Subject, or more sophisticated, specific information to aid the TextView. TextView is interested in how the TextSubject changed--whether characters were added or removed, and where.
The hint can package information about the actual changes ("deleted range 12-27") and push it to the observers. The hint essentially sends the deltas that have occurred in the subject. In practice, not all observers will be interested in every hint; they may ignore some and act as if they had received a simple update request.
A hint can be extended with additional information by making it a first-class object. This enables subjects to bundle the additional information by subclassing from a Hint base class. At the receiving end, the observer downcasts the hint to the desired type and extracts the additional information. This downcast should of course not be done in a "hard" way, and it should by guarded by using the C++ run-time type identification facilities (dynamic_cast).
When notifications are sent, the subject must be in a consistent state. If it is not, strange results may occur in the observers as they try to update themselves from a nonsensical subject.
Which object has the responsibility to actually send the notification is also important. In our example, it is the Timer object that sends notifications in its Tick operation. This works fine as long as the subject is simple.
In Listing Eight, the Tick operation is overridden in a special kind of timer that allows you to set alarms. See the problem? When subjects have the responsibility to send notifications, overriding operations in the subject may cause spurious and inconsistent notifications. By overriding Tick, the first notification (sent from Timer:: Tick) is sent while the AlarmedTimer is in an inconsistent state (the _alarm variable should be set at that time but isn't until the second notification). Some observers could set off alarms by testing the result of AlarmSet and some could do so by testing the equality of AlarmTime and CurrentTime.
There are simple fixes for this problem. But for more complex subjects with derived classes, overriding operations that send notifications in the subject could make the subject inconsistent or cause duplicate notifications to be sent.
One solution to sending notifications to the subject is making clients change the subject to initiate the notifications. Whenever the client makes a change, it must call Notify on the subject. This solution is practical, but places extra burden on the clients. It is easy to forget to call Notify on the subject. Another solution is to define Tick as a template method (see the Template Method pattern from our book) that first just calls the operation DoTick and Notify. The Timer class defines DoTick to increment the current time. Subclasses can override this operation to provide their own extensions to the Tick operation.
When a subject has complex internal state, observers may spend much effort to determine exactly what changed in the subject. Along with using hints, you can reduce this burden by relying on intrinsic properties of the subject itself. Complex subjects may only change their state in predefined ways. Changes in part of a subject's state may be independent of changes in other parts.
Such properties can be exploited by having the subject define independent aspects and having observers only subscribe to the aspects they are interested in.
In the Timer example, suppose that the Timer class were implemented with three distinct counters that maintained the time in seconds, minutes, and hours. Now the various timer displays will usually be defined in terms of hours, minutes, and seconds. Clearly, not all of these need to be updated each second. In fact, the hour, minute, and second counters change almost independently of each other. You can exploit this by defining aspects that represent changes in hours, minutes, and seconds and defining our displays as consisting of three independent parts, each subscribing to a particular part of the Timer.
In this example, assume that the aspects are simply defined as integer constants that are passed as a parameter to Notify; see Listing Nine. The class Timer makes its aspects available to the clients as class-scoped constants. In Listing Ten, for example, the changed aspect is passed as a hint to the Observer's Update operation in Listing Eleven. If there are many different aspects, the update operation becomes a lengthy conditional statement that maps an aspect to a piece of code. Such conditional code is not very elegant. There are different techniques to avoid this kind of manual-dispatching code. One technique is demonstrated in VisualWorks Smalltalk, wherein the dispatching problem is solved with a DependencyTransformer object that implements the Observer interface. It knows which aspect it is interested in and keeps track of the actual receiver of the notification and the operation to be executed by the receiver when the aspect changes. Figure 5 shows a possible class structure for a DependencyTransformer.
When a DependencyTransformer receives the update, it checks the aspect. If the aspect matches, DependencyTransformer invokes the operation on the Receiver. DependencyTransformers are created by the subject when an observer expresses its interest in a changed aspect. This requires a way to specify the operation to be called. In Smalltalk, the operation's selector name is specified; #updateSeconds, for example.
DependencyTransformers act as an intermediary between the subject and its dependent object. They map the Observer interface to an operation of the dependent object. A DependencyTransformer is therefore an example of the Adapter pattern.
Sometimes classes are not designed to be subjects, but later, you realize that instances of these classes might have dependent objects. How do you make such classes into subjects? You could change the class by mixing in the Subject interface, but this is not always possible. The class you wish to make a subject may not be modifiable--it may reside in a class library over which you have no control.
An elegant way to allow arbitrary classes to become subjects is to wrap the object in another object that adds the Subject behaviors and interfaces. This decorator object (this is an example of the Decorator pattern) intercepts and forwards all requests to the wrapped object, and notifies clients after operations which are likely to change the wrapped object.
Suppose the class Timer was in fact not designed to be a subject and was defined as in Listing Twelve. You could make Timer a subject by defining a TimerDecorator class as in Listing Thirteen. The TimerDecorator has the same interface as the Timer, so it looks like a timer to clients. Every request made of the TimerDecorator is forwarded on to the timer, and then the timer calls Notify on itself to update its observers; see Listing Fourteen.
Making objects be subjects by using decorators is only practical when the decorated object's interface is relatively small, because you have to duplicate the subject's interface in the decorator. If the subject's interface is large, this approach can become unwieldy.
In one form or another, the Observer pattern occurs in many object-oriented systems. While most commonly used for decoupling user interfaces from data to be displayed on the user interface, often it is used to manage dependencies between objects. The Observer pattern has many more possible variations than the few we've examined. For example, we did not look at batching notifications, concurrency and distribution, or observing more than one subject.
Finally, a description of the Observer pattern would not be complete without mentioning its origin in the Smalltalk's Model-View-Controller (MVC) framework. In this design, the Model encapsulates application data. The View presents the model to the user. The controller is responsible for handling user input. From a dependency-management view, MVC provides the idea of decoupling the application data from the user interface. The benefit of this decoupling is that the application data can be presented by different user interfaces. In MVC terminology, the timer object becomes the "model" and the time display becomes a "view." If we supported manipulation of the time display by the user, then this behavior would be assigned to the Controller.
Figure 1: Simple timer object. Figure 2: Class diagram for directly coupling the timer with its observer. Figure 3: Class relationships in OMT notation. Figure 4: Resulting class coupling. Figure 5: Possible class structure for a DependencyTransformer.
class DigitalTimeDisplay;
class Timer {
public:
Timer(DigitalTimeDisplay*);
long CurrentTime() const;
void Tick();
private:
DigitalTimeDisplay* _display;
long _currentTime;
};
class DigitalTimeDisplay {
public:
DigitalTimeDisplay();
void DisplayTime();
void UpdateTime(long time);
};
void Timer::Tick()
{
_currentTime++;
_display->UpdateTime(_currentTime);
}
class Subject {
public:
void Attach(Observer*);
void Detach(Observer*);
void Notify();
protected:
Subject();
private:
List<Observer*> *_observers;
};
void Subject::Notify () {
ListIterator<Observer*> i(_observers);
for (i.First(); !i.IsDone(); i.Next() ) {
i.CurrentItem()->Update();
}
}
class Timer : public Subject {
public:
Timer();
virtual void Tick();
long CurrentTime()
const;
private:
long _currentTime;
}
void Timer::Tick()
{
_currentTime++;
Notify();
}
class Observer {
public:
virtual void Update() = 0;
protected:
Observer();
};
class DigitalTimeDisplay : public Observer {
public:
DigitalTimeDisplay(Timer*);
virtual void Update();
void DisplayTime(long time);
private:
Timer* _timer;
};
DigitalTimeDisplay::DigitalTimeDisplay(Timer* t) : _timer(t)
{
}
void DigitalTimeDisplay::Update()
{
DisplayTime( _timer->CurrentTime() );
}
class AnalogTimeDisplay : public Observer {
public:
AnalogTimeDisplay(Timer*);
virtual void Update();
void DisplayTime(long time);
private:
Timer* _timer;
};
AnalogTimeDisplay::AnalogTimeDisplay(Timer* t) : _timer(t)
{
}
void AnalogTimeDisplay::Update()
{
DisplayTime( _timer->CurrentTime() );
}
class TimerObserver {
public:
virtual void Update(long) = 0;
protected:
TimerObserver();
};
void TimerSubject::Notify (long time)
{
ListIterator<TimerObserver*> i(_observers);
for (i.First(); !i.IsDone(); i.Next() ) {
i.CurrentItem()->Update(time);
}
}
class AlarmedTimer : public Timer {
public:
AlarmedTimer();
virtual void Tick();
long AlarmTime();
bool AlarmSet();
private:
long _alarmTime;
bool _alarm;
};
AlarmedTimer::AlarmedTimer()
: _alarmTime(0), _alarm(false)
{
}
void AlarmedTimer::Tick()
{
Timer::Tick();
if ( CurrentTime() == _alarmTime ) {
_alarm = true;
} else {
_alarm = false;
}
}
class Subject {
//...
void Notify(int aspect);
//...
};
class Timer: public Subject {
public:
//...
static const int ASPECT_SECONDS;
static const int ASPECT_MINUTES;
static const int ASPECT_HOURS;
//...
int Seconds() const;
int Minutes() const;
int Hours() const;
private:
int _seconds;
int _minutes;
int _hours;
};
void Timer::Tick()
{
_seconds = ++_seconds % 60;
Notify(ASPECT_SECONDS);
if ( _seconds == 0 ) {
_minutes = ++_minutes % 60;
Notify(ASPECT_MINUTES);
}
if ( _seconds ==0 && _minutes == 0 ) {
_hours = ++_hours % 24;
Notify(ASPECT_HOURS);
}
}
class AnalogTimeDisplay : public Observer {
public:
AnalogTimeDisplay(Timer*);
virtual void Update(int aspect);
void DisplayTime(long time);
private:
Timer* _timer;
};
void AnalogTimeDisplay::Update(int aspect)
{
if (aspect == Timer::ASPECT_SECONDS)
// update second hand ...
else if (aspect == Timer::ASPECT_MINUTES)
// update minute hand ...
else if (aspect == Timer::ASPECT_HOURS)
// update hour hand ...
else
// full update
}
class Timer {
public:
virtual void Tick();
long CurrentTime() const;
private:
long _currentTime;
};
void Timer::Tick()
{
_currentTime++;
}
class TimerDecorator : public Timer, public Subject {
public:
TimerDecorator(Timer*);
virtual void Tick();
private:
Timer* _timer;
};
void TimerDecorator::Tick ()
{
_timer->Tick();
Notify();
}
Copyright © 1995, Dr. Dobb's Journal