Skip to content
Part IA Michaelmas Term

The Observer Pattern

Design patterns overview showing Observer structure

Intent: Define a one-to-many dependency between objects so that when one object (the subject) changes state, all its dependents (the observers) are notified and updated automatically — without the subject knowing the concrete types of its observers.

The problem

A phone’s accelerometer reading changes continuously. Multiple UI components — a compass display, a tilt indicator, a step counter — need to react to each new reading. Without Observer, the accelerometer would need to know about and directly call each display component, coupling the sensor code to every consumer.

The solution

interface Observer {
    void onChange(double value);
}

class Accelerometer {
    private List<Observer> observers = new ArrayList<>();

    void subscribe(Observer o) {
        observers.add(o);
    }

    void unsubscribe(Observer o) {
        observers.remove(o);
    }

    void update(double reading) {
        for (Observer o : observers) {
            o.onChange(reading);
        }
    }
}

Observers implement the interface and register themselves:

class CompassDisplay implements Observer {
    public void onChange(double value) {
        System.out.println("Compass adjusted: " + value);
    }
}

class StepCounter implements Observer {
    private int steps = 0;
    public void onChange(double value) {
        if (value > THRESHOLD) steps++;
    }
}

Usage:

Accelerometer sensor = new Accelerometer();
sensor.subscribe(new CompassDisplay());
sensor.subscribe(new StepCounter());

sensor.update(0.85);  // both observers receive the notification

Why Observer works

The subject (Accelerometer) knows only about the Observer interface — never about concrete display classes. New observers can be added without modifying the subject. This is loose coupling: the subject and observers can vary independently.

It satisfies OCP: the subject is open for extension (new observers can register) but closed for modification (the subject’s code does not change).

Push vs Pull

Push model: The subject sends the data with the notification (as in the example above — onChange(double value)). Observers get all the data they might need, whether they use it or not.

Pull model: The subject notifies observers that something changed, and observers request the specific data they need from the subject.

// Pull model
interface Observer {
    void update(Subject s);  // observer calls s.getData() itself
}

The push model is simpler; the pull model is more flexible when observers need different subsets of the subject’s data.

Observer in the Java ecosystem

The Java event model is Observer. For example, ActionListener is the Observer interface, and a JButton is the Subject:

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});

The publish-subscribe messaging pattern is also Observer at a larger scale: publishers (subjects) publish messages to topics, and subscribers (observers) receive messages from topics they have subscribed to. Reactive programming frameworks (RxJava, Project Reactor) generalise the Observer pattern with functional composition.

Observer with lambdas

In modern Java, since Observer is a functional interface (one abstract method), you can register observers with lambdas:

sensor.subscribe(value -> compassDisplay.adjust(value));
sensor.subscribe(value -> stepCounter.record(value));

Or with method references if the observer’s method signature matches:

sensor.subscribe(compassDisplay::adjust);
sensor.subscribe(stepCounter::record);

This eliminates the need to write explicit observer classes for simple cases.

Tripos approach to Observer questions

A typical Tripos question: “A stock trading application needs to notify users of price changes, with users choosing how they are notified. Design a solution.”

Your answer should:

  1. Name the pattern: Observer.
  2. Sketch the participants: Subject (Stock) with subscribe/unsubscribe/notify, Observer interface, concrete observers for different notification channels.
  3. Write skeleton code for the specific domain classes.
  4. Address the notification strategy: If users choose how they are notified, this is Strategy inside Observer — each observer uses a NotificationStrategy (email, SMS) to deliver the notification.
  5. Justify: Without Observer, the Stock class would need to know about every notification channel; with Observer, new channels can be added without modifying Stock. This satisfies OCP and promotes loose coupling.

Tripos traps

  1. Forgetting to unregister — Observers that are no longer needed should be removed from the subject’s list. If an observer is garbage-collected while still registered, it is fine (it will not be called), but if the subject holds the only reference, the observer will never be GC’d — a memory leak.
  2. Concurrent modification — If an observer, in its onChange method, tries to unsubscribe itself or subscribe another observer, this modifies the list being iterated. Use a copy-on-write list (CopyOnWriteArrayList) or iterate over a snapshot.
  3. Notification order — The order in which observers are notified is typically unspecified. Do not write code that depends on a particular notification order unless you explicitly document it.