Observer Design Pattern

The Observer Design Pattern is a behavioral design pattern that allows an object, known as the subject, to maintain a list of dependents, called observers, and automatically notify them of any state changes, typically by calling one of their methods. This pattern is especially useful for creating a one-to-many relationship where changes in one object (subject) should trigger updates in other objects (observers) without tightly coupling them.

Key Components of the Observer Pattern

  1. Subject: The main object being observed. It maintains a list of observers and notifies them of state changes.

  2. Observer Interface: Defines the method(s) that observers must implement to get updates from the subject.

  3. Concrete Observers: Classes that implement the Observer interface to receive updates from the subject.

  4. Concrete Subject: Implements the subject’s behavior, notifying registered observers when its state changes.

Example Use Case: Weather Station

Imagine a Weather Station application where multiple displays (like temperature, humidity, and pressure displays) need to be updated whenever the weather data changes. Here, the weather station acts as the subject, while the various displays act as observers.

Implementation in Java (Spring Boot)

  1. Define the Observer Interface:

    public interface Observer {
        void update(float temperature, float humidity, float pressure);
    }
  2. Define the Subject Interface:

    public interface Subject {
        void registerObserver(Observer observer);
        void removeObserver(Observer observer);
        void notifyObservers();
    }
  3. Concrete Subject (WeatherStation):

    import java.util.ArrayList;
    import java.util.List;
    
    public 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();  // Notify all observers of the change
        }
    }
  4. Concrete Observers:

    // TemperatureDisplay.java
    public class TemperatureDisplay implements Observer {
        @Override
        public void update(float temperature, float humidity, float pressure) {
            System.out.println("Temperature Display: " + temperature + "°C");
        }
    }
    
    // HumidityDisplay.java
    public class HumidityDisplay implements Observer {
        @Override
        public void update(float temperature, float humidity, float pressure) {
            System.out.println("Humidity Display: " + humidity + "%");
        }
    }
    
    // PressureDisplay.java
    public class PressureDisplay implements Observer {
        @Override
        public void update(float temperature, float humidity, float pressure) {
            System.out.println("Pressure Display: " + pressure + " Pa");
        }
    }
  5. Running the Example:

    public class WeatherStationApp {
        public static void main(String[] args) {
            WeatherStation weatherStation = new WeatherStation();
    
            // Create and register observers
            TemperatureDisplay tempDisplay = new TemperatureDisplay();
            HumidityDisplay humidityDisplay = new HumidityDisplay();
            PressureDisplay pressureDisplay = new PressureDisplay();
    
            weatherStation.registerObserver(tempDisplay);
            weatherStation.registerObserver(humidityDisplay);
            weatherStation.registerObserver(pressureDisplay);
    
            // Simulate weather data updates
            weatherStation.setWeatherData(25.0f, 60.0f, 1013.0f);
            weatherStation.setWeatherData(28.0f, 55.0f, 1010.0f);
        }
    }

Explanation

  • Subject (WeatherStation): Maintains a list of observers and notifies them when the weather data changes. The setWeatherData() method simulates data updates and triggers notifications.

  • Observer Interface (Observer): Specifies the update() method that each observer must implement.

  • Concrete Observers (TemperatureDisplay, HumidityDisplay, PressureDisplay): Each display class implements Observer and defines its own update behavior for the data it is interested in.

Advantages of the Observer Pattern

  1. Loose Coupling: Subjects and observers are not tightly coupled. The subject only knows that observers implement a specific interface.

  2. Extensibility: New observers can be added without modifying the subject.

  3. Real-Time Update: Observers are notified as soon as the subject’s state changes, keeping all components synchronized.

Real-World Analogy

Consider a weather station broadcasting live updates to a set of displays (e.g., temperature, humidity, pressure) in various locations. Each display only needs to know how to interpret its specific type of data. When the weather data changes, the station notifies each display, allowing the displays to update independently of each other.

Last updated