Applied Design Patterns with Java
Behavioral :: Observer (293) {C ch 22}
Implementation
The following implementation issues are relevant to the
Observer pattern:
- Mapping subjects to their Observers.
The simplest way for a Subject
to keep track of the Observers it should notify
is to store references to them explicitly in the Subject. However,
such storage may be too expensive when there are many Subjects and few Observers. One solution is to trade space for time by using an associative
look-up (e.g., a hash table) to maintain the subject-to-Observer mapping. Thus a Subject with no Observers does
not incur storage overhead. On the other hand, this approach increases the cost of accessing the Observers.
- Observing more than one subject. It might make sense in some situations for an Observer to depend on more
than one Subject. For example, a spreadsheet may depend on more than one data source. It's necessary to
extend the Update interface in such cases to let the Observer know which Subject is sending the notification. The Subject can simply pass
itself as a parameter in the Update operation, thereby letting the Observer know which Subject to examine.
- Who triggers the update?
The Subject and its Observers rely on the notification mechanism to stay consistent. But what
object actually calls Notify to trigger the update? Here are two options:
a) Have state-setting operations on Subject call Notify after they change the Subject's state. The advantage
of this approach is that clients don't have to remember to call Notify on the Subject. The disadvantage
is that several consecutive operations will cause several consecutive updates, which may be inefficient.
b) Make clients responsible for calling Notify at the right time. The advantage here is that the client can wait
to trigger the update until after a series of state changes has been made, thereby avoiding needless intermediate
updates. The disadvantage is that clients have an added responsibility to trigger the update. That makes errors
more likely, since clients might forget to call Notify.
- Dangling references to deleted subjects. Deleting a Subject
should not produce dangling references in its Observers. One way to avoid dangling references is to make the Subject
notify its Observers as it is deleted so that they can reset their reference to it. In general, simply deleting
the Observers is not an option, because other objects may reference them, or they may be observing
other Subjects as well.
- Making sure Subject state is self-consistent
before notification. It's
important to make sure Subject state is self-consistent before calling Notify, because Observers
query the Subject for its current state in the course of updating their own state. This self-consistency
rule is easy to violate unintentionally when Subject subclass operations
call inherited operations.
- Avoiding observer-specific update protocols: the push
and pull models. Implementations
of the Observer
pattern often have the Subject broadcast additional
information about the change. The Subject passes this information as an argument to Update. The amount of
information may vary widely. At one extreme, the push model, the Subject sends Observers detailed information
about the change, whether they want it or not. At the other extreme is the pull model; the Subject sends
nothing but the most minimal notification, and observers ask for details explicitly thereafter. The pull model
emphasizes the Subject's ignorance of its Observers, whereas the push model assumes Subjects know something
about their Observer' needs. The push model might
make Observers less reusable, because Subject classes make assumptions
about Observer classes that might not always be true. On the other hand, the pull model may be inefficient,
because Observer classes must ascertain what changed without help from the Subject.
- Specifying modifications of interest explicitly. Improve update efficiency
by extending the Subject's registration interface to allow registering Observers only for specific
events of interest. When such an event occurs, the Subject informs only those Observers that have registered interest in that event.
- Encapsulating complex update semantics. When the dependency
relationship between Subjects and Observers is particularly complex, an object that maintains these relationships
might be required. Such an object is a ChangeManager. Its purpose is to minimize the work required to make Observers
reflect a change in their Subject. For example, if an operation involves changes to several interdependent
Subjects,
it may be necessary to ensure that their Observers are notified only after all the Subjects have been modified
to avoid notifying Observers more than once.
ChangeManager has three responsibilities:
a) It maps a Subject
to its Observers and provides an
interface to maintain this mapping. This eliminates the need for Subjects to maintain references
to their Observers and vice versa.
b) It defines a particular update strategy.
c) It updates all dependent Observers at the request of a
Subject.
The following diagram depicts a simple ChangeManager-based
implementation of the Observer pattern. There are two specialized ChangeManagers. SimpleChangeManager
is naive in that it always updates all Observers of each Subject. In contrast, DAGChangeManager handles directed-acyclic graphs of dependencies between Subjects
and their Observers. A DAGChangeManager is preferable to a SimpleChangeManager when an Observer observes more than one Subject. In that case, a change in two or more Subjects might
cause redundant updates. The DAGChangeManager ensures the Observer receives just one update. SimpleChangeManager is
fine when multiple updates aren't an issue.
- Combining the Subject and Observer classes. Class libraries written
in languages that lack multiple inheritance (like Java) generally don't define separate Subject and Observer classes but combine
their interfaces in one class. That allows defining an object that acts as both a Subject and an Observer
without multiple inheritance. The Subject and Observer interfaces are defined in the base class Object, making them available
to all derived classes.
Related Patterns
Mediator (273): By encapsulating complex update semantics, the ChangeManager acts as mediator between Subjects and Observers.
Singleton (127): The ChangeManager may use the
Singleton pattern to make it unique and globally accessible.
Catalog Behavioral Prev Next