Observer pattern: Your key to efficient and scalable event-handling systems

What is observer pattern

Following is a diagram that shows how an observer pattern looks like:

When is observer pattern used?

The Observer pattern is typically used in scenarios where a change in one object’s (also called a subject) state needs to be notified to one or more other objects (also called observers) and possibly react to this change. It’s highly useful in managing complex interdependencies in a manner that supports low coupling and high cohesion, thus enhancing maintainability and scalability of systems.

Following are five real-world examples illustrating the use of the Observer pattern:

  1. User Interface Components: In GUI toolkits, when a user clicks a button, the button (subject) notifies all registered listeners (observers), which are parts of the application that need to respond to the user action, such as updating a label or opening a new window.
  2. Stock Market Monitoring: The stock market feed acts as the subject, sending out updates to all registered observers, which could be individual traders’ dashboards, stock analysis software, or automated trading algorithms, updating them with the latest price movements.
  3. Social Media Notifications: Social media platforms use the Observer pattern to update users about new posts or activities from friends. For example, when a user posts new content, the system (subject) notifies all followers (observers) of the update, often triggering real-time notifications on their devices.
  4. Weather Monitoring Systems: Weather stations use the Observer pattern to update multiple displays and systems based on data from weather sensors. As the weather data changes (temperature, humidity, wind speed), the central system (subject) notifies various displays and other monitoring equipment (observers), which then update their information in real time.
  5. News Agencies: News agencies use the Observer pattern to distribute news quickly and efficiently to various subscribers, who could be individual readers, other news agencies, or media outlets. When a new piece of news is ready, it’s pushed out to all subscribers (observers) who have signed up for updates, ensuring they receive the information as soon as it’s available.

Why is observer pattern used?

The Observer pattern allows for the subjects and observers to remain independent of each other, facilitating maintenance and updates without requiring extensive changes to the overall system architecture. Following are some of the main reasons to use this pattern:

  1. Decoupling of Components: One of the most compelling reasons to use this pattern is its ability to decouple the objects that interact within a system. This means the subject does not need to know anything about the observers, only that they implement a certain interface. This separation allows for changes in the observer’s implementation without affecting the subject, leading to a more modular system that is easier to understand, develop, maintain, and test.
  2. Dynamic Subscription Management: This pattern allows observers to subscribe or unsubscribe from receiving updates dynamically at runtime. This flexibility enables systems to efficiently manage resources by only notifying active observers that need to react to certain events, thus enhancing performance and scalability.
  3. Real-time Updates: In systems where state changes need to be reflected in real-time across various components, this pattern provides an effective mechanism for immediate notification. This is crucial in scenarios like real-time trading platforms, live sports scoreboards, or sensor data monitoring, where timely information dissemination is critical.
  4. Simplifies Communication in Complex Systems: In complex systems with multiple interdependent components, managing communication paths between components can become unwieldy. This pattern streamlines communication by centralizing the management of notifications through the subject. This reduces the potential for errors and simplifies the underlying communication model.
  5. Facilitates Concurrency: By separating the state-holding subject from its observers, this pattern can naturally fit into concurrent or multi-threaded systems. Observers can handle notifications on separate threads, thereby improving the responsiveness and performance of applications.
  6. Promotes Open/Closed Principle: One of the key principles of object-oriented design is the Open/Closed Principle, which states that software entities should be open for extension but closed for modification. The Observer pattern adheres to this principle by allowing new observer types to be introduced without changing the subject’s code.

How to implement observer pattern (In Java)

In a weather station, the temperature sensor is to be monitored by the alarm system and the display system. The display system will show the temperature values and the alarm system will raise an alarm in case the temperature goes beyond a certain threshold.

First we will see the implementation without the pattern and then with the pattern

Without using observer pattern

In this version, a TemperatureSensor will directly update a Display and an AlarmSystem whenever the temperature changes. This approach directly couples the TemperatureSensor with both the display and alarm system, so next time, whenever there is another sensor to add, we will have to update the TemperatureSensor class. This reduces the code maintainability.

class TemperatureSensor {
    private int temperature;
    private Display display;
    private AlarmSystem alarmSystem;

    public TemperatureSensor(Display display, AlarmSystem alarmSystem) {
        this.display = display;
        this.alarmSystem = alarmSystem;
    }

    public void setTemperature(int newTemperature) {
        this.temperature = newTemperature;
        display.update(newTemperature);
        if (newTemperature > 100) {
            alarmSystem.activateAlarm();
        }
    }
}

class Display {
    public void update(int temperature) {
        System.out.println("Current Temperature: " + temperature + "°C");
    }
}

class AlarmSystem {
    public void activateAlarm() {
        System.out.println("Alarm activated! Temperature is too high!");
    }
}

Using Observer Pattern

Let’s refactor the code to use the Observer pattern. We’ll create interfaces Subject and Observer so that TemperatureSensor (the subject) can notify Display and AlarmSystem (the observers) of changes without being directly coupled to them. In this example, the observers can be added and removed at runtime rather than making changes to the TemperatureSensor class.

interface Observer {
    void update(int temperature);
}

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

class TemperatureSensor implements Subject {
    private int temperature;
    private List<Observer> observers = new ArrayList<>();

    public void setTemperature(int temperature) {
        this.temperature = temperature;
        notifyObservers();
    }

    @Override
    public void addObserver(Observer o) {
        observers.add(o);
    }

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

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

class Display implements Observer {
    @Override
    public void update(int temperature) {
        System.out.println("Current Temperature: " + temperature + "°C");
    }
}

class AlarmSystem implements Observer {
    @Override
    public void update(int temperature) {
        if (temperature > 100) {
            System.out.println("Alarm activated! Temperature is too high!");
        }
    }
}

// Usage:
public class Main {
    public static void main(String[] args) {
        TemperatureSensor sensor = new TemperatureSensor();
        Display display = new Display();
        AlarmSystem alarm = new AlarmSystem();

        sensor.addObserver(display);
        sensor.addObserver(alarm);

        sensor.setTemperature(90);
        sensor.setTemperature(101);
    }
}
See more in