Behavioral Patterns «Prev Next»

Lesson 6 Observer structure
Objective Write a TimeObserver Interface.

Write a TimeObserver Interface

The Observer Pattern uses two abstractions that work together to implement push-based notification:

  1. Observer: the listener interface that declares the update() method
  2. Observed (also called Subject): the publisher that maintains the list of registered observers and notifies them when its state changes

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

observer Pattern Characteristics

The graphic illustrates a class diagram implementing the Observer Design Pattern. The four key components are:

  1. Observed (Subject):
    • Represents the subject in the Observer Pattern.
    • Contains a collection of observers (theObservers) to maintain the list of objects interested in being notified of changes.
    • Attributes:
      • theObservers : Observer[]: a collection or list of Observer instances.
    • Methods:
      • +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.
  2. Observer (Interface):
    • Defines the contract for observer objects.
    • Methods:
      • +update(): implemented by concrete observers; called by the subject when it notifies its observers.
  3. ConcreteObserved (Concrete Subject):
    • A concrete implementation of the Observed class.
    • Contains the state that observers are interested in.
    • Attributes:
      • state: represents the internal state of the subject.
    • Methods:
      • +getState(): retrieves the current state.
      • +changeState(): modifies the state and notifies observers of the change.
  4. Concrete Observer:
    • Implements the Observer interface.
    • Responds to notifications sent by the subject when its state changes.
    • Must implement the +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.

  1. Each Observer object implements an update() method that the Observed object invokes when its state changes.
  2. The Observer pattern requires the Observed object to notify all registered Observer objects whenever it changes.

When to Use the Observer Pattern

Use the Observer pattern in any of the following situations:

  1. When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
  2. When a change to one object requires changing others, and you do not know how many objects need to be changed.
  3. When an object should be able to notify other objects without making assumptions about who those objects are. In other words, you do not want these objects tightly coupled.

Observer Pattern as an Alternative to MVC

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:

  1. The Subject holds a given state.
  2. The observers subscribe to the subject to be informed of the current state.

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.

Figure 6-6: Observer class diagram
Figure 6-6: 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.

Observed Object

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.

Observer Interface and the java.util Deprecation

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 subscribers
  • Flow.Subscriber<T> replaces Observer as the listener that receives items and state change signals
  • Flow.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:

  1. the set of Observer objects and
  2. the Subject object.

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.

MVC within the Context of Observer

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:

  1. The controller tells the model to insert the text the user typed.
  2. The model notifies all views of a change.
  3. All views repaint themselves.
  4. During painting, each view asks the model for the current text.

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:

  1. The model knows about its observers, which are the views.
  2. An observer is an object that is interested in state changes of the model.
  3. The model knows nothing in detail about its observers except that it must notify them whenever the model data changes.

Observed Object - Exercise

In this exercise, you will write a TimeObserver interface for the course project.
Observed Object - Exercise

SEMrush Software 6 SEMrush Banner 6