Observer Design Pattern

Observer is a behavioral design pattern that establishes a one-to-many dependency between objects. When one object (the Subject) changes state, all dependent objects (Observers) will get automatically notified and updated. Here actual set of dependent objects can be unknown beforehand or change dynamically.

The beauty of the Observer pattern lies in its promotion of loose coupling between the subject and its observers, as they only depend on a common interface. Due to this, we can easily add or remove observers without modifying the subject. This enhances maintainability, extensibility, and reusability by segregating the concerns of the subject and its observers.

Real life analogy

Suppose there is an online news delivery service and a group of subscribers who want to receive daily news. Here news delivery service act as the subject, while newspaper subscribers are observers.

  • News delivery service maintains a list of subscribers and provides methods for new users to subscribe or for existing users to unsubscribe.
  • Each subscriber registers with the news delivery service.
  • When the news delivery service receives new news, it iterates over the list of subscribers and notifies each subscriber.
  • Each subscriber receives the notification and retrieves the news from their mailbox. They can then read the news or take any other action based on the received information.

Here news service doesn’t need to know the specific details of each subscriber; it only needs to know that they are interested in receiving the news. So, new subscribers can be added or removed without affecting the news delivery service. Similarly, new delivery services can be introduced in the future without modifying the existing subscribers.

Structure and components

Structure and components of observer pattern

  1. Subject (Observable): An interface that defines methods to add, remove and notify observers.
  2. Observer: An interface that declares the update method, which is called by the subject whenever a state change occurs. Update method takes relevant information as arguments and enables the observer to retrieve the updated data.
  3. Concrete Subject: A specific implementation of the subject interface. It manages a list of observers and maintains the state that observers are interested in. So whenever the state changes, it iterates over the list of observers and sends notifications by calling the update method on each observer. It also implements the methods for existing observers to leave and new observers to join the list.
  4. Concrete Observer: A specific implementation of the observer interface. It registers with a concrete subject and implements the update method to perform some actions after receiving the notification from the subject. There can be multiple concrete observers, and each can be interested in different aspects of the subject’s state.

Note

  • Sometimes, observers need some additional details to implement the update method. For this, the subject can provide those details as an argument to the update method. If required, the subject can pass itself as an argument and let the observer fetch any required data directly using the get method of the subject.
  • Inside the client code, we create subject and observer objects and then register observers to receive updates whenever any state change happens.

Let's understand via real-life example

Suppose our goal is to implement a weather monitoring system where we want to notify various displays whenever the weather conditions (temperature, humidity, and barometric pressure) change.

  • Suppose we have three different displays to show current conditions, weather statistics, and a simple forecast. All these displays should update in real-time as we acquire the most recent measurements of the weather conditions.
  • This system should be expandable. For example, we need to release an API so that other developers can write their weather displays and integrate them easily.

Solution using observer pattern

In the above problem, the weather monitoring system acts as the subject, while the displays are the observers.

Step 1: We define the Subject interface and include methods to register, remove and notify all observers.

// Interface for Subject
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

Step 2: Now we define a concrete subject (WeatherStation) which implements the Subject interface. Here concrete subjects will include these attributes and methods:

  • A list to store observers.
  • Three variables to manage the state of weather: temperature, pressure, and humidity.
  • registerObserver(Observer observer): When an observer registers, we add it to the list using this method.
  • removeObserver(Observer observer): When an observer wants to un-register, we remove it from the list using this method.
  • setWeatherData(float temperature, float humidity, float pressure): We get updated weather data using this method. So whenever we get fresh data, we call notifyObservers() inside this method.
  • notifyObservers(): This will notify all the observers (stored in the list) about the weather data change by calling the update() method of each observer. Here we can pass the new state of temperature, humidity, and pressure to the update method.

Note: If required, we can also implement get methods to fetch the state variables.

// Concrete subject
class WeatherStation implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherStation() {
        observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void setWeatherData(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
}

Step 3: Now we define the Observer interface with the update method.

interface Observer {
    void update(float temperature, float humidity, float pressure);
}

Step 4: Now we define all three concrete observers (current conditions display, forecast display, statistics display) which implement the observer interface and provide implementation of the update() method.

Each display first receives the update notification and retrieves the updated weather data. It will then update its display or perform any other actions based on the received data. We can also maintain state variables inside observers to keep some or all received weather data.

Inside each observer, we can keep a reference of the WeatherStation and initialize it inside the constructor. Why are we doing this? There could be several reasons: 

  • We use this to call the registerObserver() method inside the constructor. So whenever the client creates an instance of the concrete observer by passing the reference of the WeatherStation object, it will automatically register the observer with the WeatherStation.
  • We use this to call the removeObserver() method. For this, we define an unregister() method inside the observer, which will call removeObserver() on the WeatherStation. 
  • Suppose in the future, some observer wants to get some particular set of weather data, then it can directly fetch the required data by calling the get methods on the WeatherStation.

Implementation of CurrentConditionsDisplay class

// Concrete observer 1
class CurrentConditionsDisplay implements Observer {
    private Subject weatherStation;
    private float temperature;
    private float humidity;
    private float pressure;

    public CurrentConditionsDisplay(Subject weatherStation) {
        this.weatherStation = weatherStation;
        weatherStation.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees, " + humidity + "% humidity, " + pressure + " pressure");
    }

    public void unregister() {
        weatherStation.removeObserver(this);
    }
}

Implementation of ForecastDisplay class

// Concrete observer 2
class ForecastDisplay implements Observer {
    private Subject weatherStation;

    public ForecastDisplay(Subject weatherStation) {
        this.weatherStation = weatherStation;
        weatherStation.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        display();
    }

    public void display() {
        System.out.println("Forecast: More of the same");
    }

    public void unregister() {
        weatherStation.removeObserver(this);
    }
}

Implementation of StatisticsDisplay class

// Concrete observer 3
class StatisticsDisplay implements Observer {
    private Subject weatherStation;
    private float temperature;
    private float humidity;
    private float pressure;

    public StatisticsDisplay(Subject weatherStation) {
        this.weatherStation = weatherStation;
        weatherStation.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    public void display() {
        System.out.println("Statistics: " + temperature + "F degrees, " + humidity + "% humidity, " + pressure + " pressure");
    }

    public void unregister() {
        weatherStation.removeObserver(this);
    }
}

Step 5: Inside the client code: We first create an object of the WeatherStation (subject). Then we create instances of various display classes (observers) by passing the reference of WeatherStation object. As mentioned above, this will register each display object by calling registerObserver() inside the constructor of each observer.

Now we simulate the new weather updates by calling the setWeatherData() method with different values of temperature, humidity, and pressure. On each call, it notifies all registered observers about the new measurements. Note: setWeatherData() may be receiving updates from some other parts of the system.

After a few weather updates, we have removed the forecast display from the list of displays using the unregister() method. As mentioned above, this method will call the removeObserver() method using the reference of the WeatherStation. Now after this, forecast display will not receive the notification about the new weather data.

Note: We can also directly call the registerObserver() and removeObserver() methods on the instance of the WeatherStation. What would be difference between this and above approach? Think and explore.

// Demo
class ObserverPatternDemo {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherStation);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherStation);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherStation);

        // Simulate weather updates
        weatherStation.setWeatherData(80, 65, 30.4f);
        weatherStation.setWeatherData(82, 70, 29.2f);
        weatherStation.setWeatherData(78, 90, 29.2f);
        // Unregister an observer
        forecastDisplay.unregister();
        // Simulate weather updates after unregistering an observer
        weatherStation.setWeatherData(77, 80, 29.2f);
        // Unregister another observer
        currentConditionsDisplay.unregister();
        // Simulate weather updates after unregistering an observer
        weatherStation.setWeatherData(75, 85, 29.0f);
    }
}

Note: There can be other different ways to implement observer Pattern, but most of them will be based on the design that includes Subject and Observer interfaces.

Weather station problem solution using observer pattern

Key takeaway from the above implementation

  • WeatherStation doesn’t need to know the specific details of each display. So, we have achieved loose coupling between WeatherStation and displays.
  • We can easily add new types of displays in the future without modifying the existing code of the WeatherStation. For example, suppose a new display class wants to be an observer. All we need to do is implement the Observer interface in the new display class and register it as an observer.
  • We can easily replace any display class at runtime with another display class, and the WeatherStation will keep functioning smoothly. Similarly, we can remove displays at any time.

Steps to implement observer pattern

  1. Define a Subject interface with the methods for registering, removing, and notifying observers.
  2. Create a concrete subject class that implements the Subject interface. This will maintain a list of observers and provide methods to get and set the state that observers are interested in.
  3. Define an Observer interface that defines the method that observers must implement to receive updates from the subject.
  4. Create concrete observer classes that implement the Observer interface. They can also maintain any necessary internal state to store the received information.
  5. Create instances of the subject class and observer classes in your application.
  6. Now you can register or unregister observer instances.

Pros and cons of the observer pattern

Pros

  • Promote loose coupling: Changes to either the subject or an observer will not affect the other.
  • Open/Closed principle: We can add new observers to a subject without modifying its code.
  • Reusability: We can reuse observers across different subjects because attaching multiple observers to different subjects become easy.
  • Better data control: Here subject is the exclusive owner of the data. So this one of the good OOP design compared to the situation where multiple objects are controlling the data.

Cons:

  • Each observer must be notified individually. So there can be some performance overhead, especially when there are a large number of observers and frequent updates.
  • The order in which observers are notified may not be deterministic. This can be a concern if the order of execution matters.
  • Observers may receive updates that they are not interested in or are not relevant to their current state. This can lead to unnecessary processing if observers need to filter out irrelevant updates.
  • As the number of observers and subjects increases, managing the relationships between them can become more complex.

Relations with other patterns

  • Similar to Observer, we also use Chain of Responsibility, Command and Mediator to connect senders and receivers in various ways.
  • Mediator focuses on coordinating between objects by centralising the communication logic in a mediator object. So, Observer can be used within the Mediator to notify interested objects about changes or events. Here Mediator can act as the subject and the objects it communicates with can be the observers.
  • In some cases, subject can be implemented as the Singleton in the Observer pattern. This allows for a centralized point of control.
  • In Model-View-Controller (MVC), Observer is often used to establish communication between the model and the view. The view observes the model for changes and updates its presentation accordingly. This decouples the model and view components and keeps them synchronized.

Applications of observer pattern

  • When an event occurs in event-driven systems, there is a need to notify and update multiple components or modules. So observer pattern helps us to implement this mechanism.
  • During UI development, sometimes changes in one part of the UI often need to be reflected in other parts. For example, when a user modifies data, we may need to update other UI components accordingly. We can implement this functionality using the observer pattern.
  • In publish-subscribe systems, publishers generate messages and subscribers are interested in receiving and taking actions based on those messages. Here publishers act as subjects, while subscribers act as observers.
  • In multi-threaded environments, we can use observer patterns to synchronize data across different threads. Observers can listen for changes in the shared data and update their local copies or perform required actions based on the changes.

If you have any queries or feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning!

More from EnjoyAlgorithms

Self-paced Courses and Blogs