Skip to main content

Command Palette

Search for a command to run...

Observer Design Pattern – Head First Approach

Stay Notified: Leveraging the Observer Pattern for Dynamic Updates

Updated
5 min read
Observer Design Pattern – Head First Approach
A

As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.

The Observer Pattern is another essential design pattern that allows objects to automatically receive updates when another object changes. In the Head First Design Patterns book, the observer pattern is introduced as a way to define a one-to-many relationship, where multiple observers (listeners) are notified of any changes to a subject (observable).

Let's dive into the observer pattern by exploring its definition, practical use cases, and implementation with examples inspired by the Head First approach.


What is the Observer Pattern?

Definition: The observer pattern defines a one-to-many dependency between objects so that when one object (the subject) changes state, all of its dependents (observers) are notified and updated automatically.

This pattern is widely used in systems that require real-time updates, such as user interfaces, event-driven architectures, and data streaming applications. It helps to maintain loose coupling between objects because the subject and the observers can operate independently.


Problem Scenario

Imagine you are building a weather station that provides real-time weather updates. The weather station monitors temperature, humidity, and pressure. Multiple display devices (e.g., current conditions display, statistics display) need to update their data whenever there is a change in weather.

Without the observer pattern, you would need to tightly couple the weather station with each display, making the system difficult to maintain or extend. If we add or remove new displays, we would need to modify the weather station itself, which violates the open-closed principle.


Using the Observer Pattern

The observer pattern allows you to separate the weather station (subject) from the displays (observers). When the weather station updates its data, it automatically notifies all registered observers. This way, we can add or remove observers without modifying the core weather station code.

Step 1: Define the Subject Interface

The Subject interface is responsible for adding, removing, and notifying observers.

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

Step 2: Define the Observer Interface

The Observer interface is implemented by any class that wants to receive updates from the subject. The update() method is called whenever the subject’s state changes.

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

Step 3: Implement the WeatherData (Subject)

The WeatherData class acts as the subject, holding the weather data and notifying observers when there’s an update.

import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

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

    public void registerObserver(Observer o) {
        observers.add(o);
    }

    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

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

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

Step 4: Implement the Displays (Observers)

Each display implements the Observer interface and defines how it handles the updated weather data. Here's an example for a CurrentConditionsDisplay.

public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    private Subject weatherData;

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

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

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

Similarly, you can create other displays like StatisticsDisplay and ForecastDisplay:

public class StatisticsDisplay implements Observer {
    // Implementation similar to CurrentConditionsDisplay
}

public class ForecastDisplay implements Observer {
    // Implementation similar to CurrentConditionsDisplay
}

Step 5: Simulate the Weather Station

Finally, we simulate the weather station updating data and notifying observers.

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        // Simulate new weather measurements
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

Output:

Current conditions: 80F degrees and 65% humidity
Current conditions: 82F degrees and 70% humidity
Current conditions: 78F degrees and 90% humidity

Bullet Points

  • Loose coupling: The observer pattern ensures loose coupling between the subject (WeatherData) and observers (displays). The subject doesn’t need to know the details of its observers.

  • Open for extension: New observers can be added without modifying the subject, making the system easy to extend.

  • Push vs. pull: The subject can "push" updates to observers (by passing data as arguments) or allow observers to "pull" data (by requesting it from the subject).


When to Use the Observer Pattern

  • When you need to notify multiple objects of state changes, especially in event-driven systems.

  • When changes in one object should automatically trigger updates in dependent objects.

  • When you want to decouple the subject and observers, allowing for greater flexibility.


Conclusion

The observer pattern provides a powerful way to design systems where objects can communicate state changes without being tightly coupled. This pattern shines in event-driven architectures, allowing multiple observers to react to changes in the subject automatically.

With the observer pattern, as outlined in Head First Design Patterns, your code becomes more flexible, maintainable, and open to new functionalities. You can easily add or remove observers, ensuring that your system is both scalable and efficient.