| Lesson 5 | Observer: motivation |
| Objective | Define the Motivation of the Observer pattern. |
Like many other patterns, the Observer pattern appears outside of software engineering in everyday situations. Suppose a bookstore has dozens of customers waiting for the latest novel to arrive. Which approach is more efficient?
The second approach is clearly more efficient. That is the core of the Observer pattern. The object whose state changes, called the subject or observable, maintains a list of interested parties, called observers, and notifies every observer on the list automatically when its state changes. Observers do not need to poll for updates. The subject takes responsibility for delivering the notification.
The Observer design pattern enables a subscriber to register with and receive notifications from a provider. It is suitable for any scenario that requires push-based notification rather than polling. The pattern defines a provider, also known as a subject or observable, and zero, one, or more observers. Observers register with the provider, and whenever a predefined condition, event, or state change occurs, the provider automatically notifies all observers by calling one of their methods. In this call, the provider can also supply the current state information so that observers do not need to query the subject separately.
Have you ever used an application that shows two editable views of the same data simultaneously, such as a source editor and a live rendered preview? When you edit one view, the other updates automatically and instantly. Modern code editors with split-pane views, documentation tools with live Markdown preview, and integrated development environments with dual-panel editors all exhibit this behavior.
The question of how this behavior is implemented has two parts:
The answer is the Model-View-Controller (MVC) architecture, which separates data from its visual representations and makes the Observer pattern the mechanism that keeps all views synchronized:
Adding a third view requires no changes to the model or the existing views. The new view simply registers itself as an observer with the model and begins receiving notifications. This is the extensibility benefit the Observer pattern provides.
It is common for one object in a system to need to know about changes in the state of another object. Several real-world examples illustrate this need:
FontDialog object
changes from Times to Helvetica so the selected text can be reformatted immediately.In the course project, vehicle queues need to know when the state of the traffic
light changes. A naive solution is to have VehicleQueue run a thread that
continuously polls the light for its current state. This polling approach wastes CPU
cycles and couples the queue tightly to the light's implementation. The better solution
is to apply the Observer pattern: the traffic light, as subject, takes responsibility
for notifying all registered VehicleQueue observers whenever its state
changes. The queues receive a push notification and react accordingly.
This is possible because of data encapsulation. The only way a subject's state can change is through its own methods. Adding notification code to each state-changing method is straightforward and keeps the notification logic centralized in the subject rather than distributed across all interested parties.
Use the Observer pattern when:
When displaying data in a graphical user interface, it is common to represent the same data in more than one form simultaneously and have all representations update together whenever the underlying data changes. A stock price application might show the same price data as both a line graph and a sortable table. When a new price arrives, both the graph and the table update without any user action. The Observer pattern is the mechanism that makes this possible.
The Observer pattern assumes that the object containing the data, the subject, is separate from the objects that display the data, the observers. This separation is illustrated in the diagram below.
When implementing the Observer pattern, the data object is referred to as the Subject and each display object is referred to as an Observer. Each observer registers its interest in the subject by calling a public method on the subject. The subject then calls a known method on each registered observer whenever its data changes.
In modern Java, the interface contracts for Subject and Observer are defined without
the redundant abstract keyword, which is implicit on all interface methods
since Java 8. The @FunctionalInterface annotation on Observer indicates
that it has exactly one abstract method, enabling lambda expressions as observer
implementations:
@FunctionalInterface
interface Observer {
// Notify this observer that the subject's state has changed
void update(String state);
}
interface Subject {
// Register an observer to receive state change notifications
void registerInterest(Observer observer);
// Remove a previously registered observer
void removeObserver(Observer observer);
// Notify all registered observers of a state change
void notifyObservers();
}
The @FunctionalInterface annotation on Observer allows
any lambda expression to serve as an observer registration, for example:
subject.registerInterest(state -> System.out.println("State changed to: " + state));
The advantage of defining these interfaces is that any class implementing
Subject and Observer can participate in the pattern regardless
of its other responsibilities. Java interface default methods, introduced in Java 8,
extend this advantage further: an interface can now provide a default implementation
of optional methods such as removeObserver, reducing boilerplate in
concrete observer classes that do not need to support deregistration.
Since Java 9, the standard library provides java.util.concurrent.Flow
as the canonical reactive Observer implementation with backpressure support:
Flow.Publisher<T> corresponds to Subject: it publishes items to
registered subscribersFlow.Subscriber<T> corresponds to Observer: it receives items and
completion or error signalsFlow.Subscription manages the lifecycle of a subscription, including
backpressure via the request(n) method that allows subscribers to control
how many items they receiveThe classic java.util.Observable class, which was the GoF-era Java
Observer implementation, was deprecated in Java 9 due to design limitations including
non-generic types and the requirement that subjects extend a concrete class rather than
implement an interface. For new Java code, use java.util.concurrent.Flow
or a reactive framework. RxJava and Project Reactor implement the Observer pattern at
scale for asynchronous event streams and are widely used in modern Java application
development.