Behavioral Patterns «Prev Next»

Lesson 5 Observer: motivation
Objective Define the Motivation of the Observer pattern.

Observer Pattern Motivation

Motivation for 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?

  1. Each customer visits the store every day and asks the clerk whether the book has arrived. The clerk answers no and asks them to come back tomorrow. This repeats until the book arrives.
  2. The clerk takes each customer's name and contact information, adds it to a list, and contacts everyone on the list as soon as the book arrives.

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.

Push-Based 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.

WYSIWYG Dual-View Editing and MVC Architecture

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:

  1. When you type in one view, how does the change appear immediately in the other view?
  2. What happens when a third view of the same data is added?

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:

  1. Model: one object holds the information in a data structure, for example an array of numbers, a tree, or a JavaBean. The model has no visual appearance and holds only the raw data. It is the subject in Observer terms.
  2. View: one or more objects render the visible representation of the data in a format specific to that view. A table view displays numbers in a table. A graph view displays the same numbers as a bar chart. A text editor view displays the document as formatted prose. Each view is an observer registered with the model.
  3. Controller: each view has a controller that processes user interaction, including keyboard input, mouse events, and UI elements such as buttons and menus. When the user edits via the controller, the controller updates the model, which then notifies all registered views to refresh their display.

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.

Changes in State of Another Object in a System

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:

  1. A WebSocket client needs to know when the connection state changes from open to closed, indicating a dropped connection that requires reconnection logic.
  2. An IoT dashboard needs to know when a temperature sensor reading crosses a threshold, triggering an alert notification to registered monitoring components.
  3. A document editor needs to know when the state of a 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:

  1. Changes to one object imply possible changes in one or more other objects.
  2. The object that changes does not know at compile time exactly which other objects need to be notified of its changes.

Mechanics of the Observer Pattern

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.

The data is separate from the Graphics and List Display
The "Data" is separate from the Graphics and List Display

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));

Modern Java Observer: java.util.concurrent.Flow

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 subscribers
  • Flow.Subscriber<T> corresponds to Observer: it receives items and completion or error signals
  • Flow.Subscription manages the lifecycle of a subscription, including backpressure via the request(n) method that allows subscribers to control how many items they receive

The 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.


SEMrush Software 5 SEMrush Banner 5