| Lesson 6 | Observer structure |
| Objective | Write a TimeObserver Interface. |
The Observer Pattern uses two abstractions that work together to implement push-based notification:
Observer: the listener interface that declares the
update() methodObserved (also called Subject): the publisher
that maintains the list of registered observers and notifies them when its
state changesThe Observer Pattern applies whenever there is a one-to-many relationship between objects: when one object is modified, all dependent objects must be notified automatically. The class diagram below illustrates the structure.
The graphic illustrates a class diagram implementing the Observer Design Pattern. The four key components are:
Observed (Subject):
theObservers) to
maintain the list of objects interested in being notified of changes.theObservers : Observer[]: a collection or list of
Observer instances.+addObserver(o: Observer): void: adds an observer to the
list.+removeObserver(o: Observer): void: removes an observer
from the list.+notifyObservers(): void: notifies all registered observers
of a state change.Observer (Interface):
+update(): implemented by concrete observers; called by the
subject when it notifies its observers.ConcreteObserved (Concrete Subject):
Observed class.state: represents the internal state of the subject.+getState(): retrieves the current state.+changeState(): modifies the state and notifies observers
of the change.Observer interface.+update() method.Relationships:
Observed depends on Observer:
the subject holds a reference to the Observer interface, enabling
any class that implements Observer to register as a listener.ConcreteObserved extends Observed:
adds state-specific logic to the base subject behavior.Observer implementation: concrete classes
must implement the update() method to respond to subject
notifications.Purpose: the diagram demonstrates how the Observer Design Pattern decouples the subject from its observers. Observers can dynamically register, deregister, or respond to state changes without the subject knowing anything about the concrete observer implementations.
update() method that the
Observed object invokes when its state changes.Use the Observer pattern in any of the following situations:
The Observer pattern provides the foundation for the Model-View-Controller architecture. At the root of the Observer Pattern are the Subject and Observer interfaces:
You can think of this as a blog with many subscribers. One set of information is routinely updated for a variety of users who subscribe or regularly read the blog. Each time the blog is updated, its state changes, and all subscribers are informed. Figure 6-6 shows the Observer class diagram.
One of the notable features of the Subject in Figure 6-6 is that none of its
methods are italicized. In UML 2.x, italicized method names indicate abstract
methods. The Subject is drawn here as a concrete abstract class rather than a
pure interface. In modern Java, Subject is more idiomatically expressed as an
interface, which avoids the single-inheritance limitation that made
java.util.Observable problematic. The diagram still clearly
identifies which methods Subject provides, and the Notify() method
includes pseudocode to illustrate its responsibility. You will find several
different implementations of the Observer pattern in the exercises that follow.
The Observed object must keep a list of the objects observing
it. It must provide an interface for adding objects to and removing objects from
that list. It also contains the methods that result in changes to its state.
Every time the state of the Observed object changes, it cycles
through the list and notifies each Observer in turn. The
Observer interface declares the update() method
that the Observed object invokes. This method performs whatever
action the concrete observer class requires in response to the notification.
The java.util package originally provided an interface named
Observer and a class named Observable. Neither
is recommended for use in modern Java. The core problem is that
java.util.Observable is a class rather than an interface. To use
it, your subject must extend Observable. In Java's single
inheritance hierarchy, this is a significant impediment: if your class already
extends another class, it cannot also extend Observable. Both
java.util.Observable and java.util.Observer were
deprecated in Java 9 for exactly this reason and are no longer part of
best-practice Java development.
The modern replacement is java.util.concurrent.Flow, introduced
in Java 9 as the standard reactive Observer implementation:
Flow.Publisher<T> replaces Observable as
the subject that pushes items to subscribersFlow.Subscriber<T> replaces Observer as
the listener that receives items and state change signalsFlow.Subscription manages the subscription lifecycle with
backpressure support via request(n)For this course, you will implement the Observer contract directly using
a custom interface rather than extending the deprecated
java.util.Observable. The modern interface declaration removes
the redundant abstract keyword and uses the
@FunctionalInterface annotation to signal that the interface has
exactly one abstract method, enabling lambda expressions as observer
implementations:
// Modern Java Observer contract (Java 9+)
// java.util.Observer is deprecated -- use this pattern instead
@FunctionalInterface
public interface Observer {
void update(String state);
}
A class implements the Observer interface when it wants to be
informed of changes in the subject. After applying the Observer pattern,
different observers can be added dynamically without requiring any changes to
the Subject class. Similarly, observers remain unaffected when the state change
logic of the subject changes.
The Observer pattern establishes a consistent communication model between a set of dependent objects and the object they depend on. This allows dependent objects to have their state synchronized with the subject. The set of dependent objects are the observers and the object they depend on is the subject. The Observer pattern achieves this through a publisher-subscriber model that creates a clear boundary between:
As introduced in the previous lesson, java.util.concurrent.Flow
implements this publisher-subscriber model with backpressure support for
asynchronous streams. The TimeObserver interface developed in the
exercise below follows the same single-method contract as
Flow.Subscriber: one update() method that receives
state change notification from the subject.
In the Model-View-Controller (MVC) architecture, the model holds the real data, and the views render approximations of it. Think of a model who poses for a painting: the model is real, and different artists observe the same model and produce different paintings. Here is what happens when a user types text into one of the views:
This architecture minimizes coupling between the model, views, and controllers. The model knows nothing about the views except that they need to be notified of all changes. The views know nothing of the controllers, making it straightforward to add more views to a model or change the controller of a view, for example to support voice input. The notification mechanism follows three rules:
TimeObserver interface for the course project.