The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When the state of one object (the Subject) changes, all dependent objects (the Observers) are notified and updated automatically. This pattern is also known as the “publish-subscribe” pattern.
Core Roles of the Observer Pattern
- Subject: The object being observed, which maintains a list of observers and provides interfaces for adding, removing, and notifying observers.
- Observer: Defines an interface for receiving notifications. It gets notified and updates when the subject’s state changes.
- Concrete Subject: Implements the subject interface, maintains state, and notifies all observers when the state changes.
- Concrete Observer: Implements the observer interface and defines specific behavior upon receiving notifications.
Example Implementation of the Observer Pattern
Below is an example demonstrating the implementation of the observer pattern using a “Weather Station System”. The weather station (subject) regularly updates weather data, and multiple display devices (observers) show the latest weather information in real-time:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// Forward declaration
class Subject;
// Observer interface
class Observer {
public:
virtual void update(float temperature, float humidity, float pressure) = 0;
virtual ~Observer() = default;
};
// Subject interface
class Subject {
public:
virtual void registerObserver(Observer* observer) = 0;
virtual void removeObserver(Observer* observer) = 0;
virtual void notifyObservers() = 0;
virtual ~Subject() = default;
};
// Concrete Subject: Weather Data
class WeatherData :public Subject {
private:
std::vector<Observer*> observers; // List of observers
float temperature; // Temperature
float humidity; // Humidity
float pressure; // Pressure
public:
void registerObserver(Observer* observer) override {
observers.push_back(observer);
}
void removeObserver(Observer* observer) override {
for (auto it = observers.begin(); it != observers.end(); ++it) {
if (*it == observer) {
observers.erase(it);
break;
}
}
}
void notifyObservers() override {
// Notify all observers
for (Observer* observer : observers) {
observer->update(temperature, humidity, pressure);
}
}
// Called when weather data updates
void measurementsChanged() {
notifyObservers();
}
// Set weather data
void setMeasurements(float temp, float humi, float press) {
temperature = temp;
humidity = humi;
pressure = press;
measurementsChanged(); // Data changed, notify observers
}
};
// Concrete Observer: Current Conditions Display
class CurrentConditionsDisplay :public Observer {
private:
float temperature;
float humidity;
Subject* weatherData; // Reference to subject for later unsubscription
public:
CurrentConditionsDisplay(Subject* subject) : weatherData(subject) {
// Register as an observer
weatherData->registerObserver(this);
}
~CurrentConditionsDisplay() override {
// Unregister
weatherData->removeObserver(this);
}
void update(float temp, float humi, float press) override {
temperature = temp;
humidity = humi;
display();
}
void display() const {
std::cout << "Current weather conditions: " << temperature
<< "°C, Humidity " << humidity << "%" << std::endl;
}
};
// Concrete Observer: Statistics Display
class StatisticsDisplay :public Observer {
private:
float maxTemp;
float minTemp;
float avgTemp;
int count;
Subject* weatherData;
public:
StatisticsDisplay(Subject* subject) : weatherData(subject) {
maxTemp = -1000;
minTemp = 1000;
avgTemp = 0;
count = 0;
weatherData->registerObserver(this);
}
~StatisticsDisplay() override {
weatherData->removeObserver(this);
}
void update(float temp, float humi, float press) override {
maxTemp = std::max(maxTemp, temp);
minTemp = std::min(minTemp, temp);
avgTemp = (avgTemp * count + temp) / (count + 1);
count++;
display();
}
void display() const {
std::cout << "Temperature Statistics: Average " << avgTemp
<< "°C, Max " << maxTemp
<< "°C, Min " << minTemp << "°C" << std::endl;
}
};
// Client usage
int main() {
// Create subject: Weather Data
WeatherData* weatherData = new WeatherData();
// Create observers: Various displays
Observer* currentDisplay = new CurrentConditionsDisplay(weatherData);
Observer* statsDisplay = new StatisticsDisplay(weatherData);
// Simulate weather data updates
std::cout << "=== First Update ===" << std::endl;
weatherData->setMeasurements(25.5f, 65.0f, 1013.0f);
std::cout << "\n=== Second Update ===" << std::endl;
weatherData->setMeasurements(26.3f, 63.0f, 1012.5f);
std::cout << "\n=== Third Update ===" << std::endl;
weatherData->setMeasurements(24.8f, 68.0f, 1013.5f);
// Clean up resources
delete statsDisplay;
delete currentDisplay;
delete weatherData;
return 0;
}
Workflow of the Observer Pattern
- The observer registers itself through the subject’s
<span>registerObserver()</span>method. - When the subject’s state changes, it calls the
<span>notifyObservers()</span>method. - The subject iterates through all registered observers and calls their
<span>update()</span>method to pass the new state. - The observers receive notifications and update themselves based on the new state.
- The observer can unsubscribe using the
<span>removeObserver()</span>method.
Push Model vs Pull Model
There are two data transmission methods in the observer pattern:
- Push Model: The subject actively pushes detailed data to the observers (as shown in the example).
- Pull Model: The subject only notifies of changes, and observers actively pull the required data from the subject.
The push model is more direct, while the pull model is more flexible, allowing observers to fetch data as needed.
Application Scenarios of the Observer Pattern
- Event handling systems (e.g., button click events in GUIs).
- Publish-subscribe systems (e.g., message queues, notification systems).
- Monitoring systems (e.g., performance monitoring, status monitoring).
- Data synchronization scenarios (e.g., multiple views displaying the same data).
Advantages and Disadvantages of the Observer Pattern
Advantages:
- Decouples the subject and observers, allowing them to change independently.
- Supports broadcast communication, allowing the subject’s state changes to notify all observers.
- Follows the open-closed principle, allowing new observers to be added without modifying the subject’s code.
- Observers can be dynamically added or removed, providing high flexibility.
Disadvantages:
- If there are many observers, notifications may take a considerable amount of time.
- Observers have no priority, so notification order cannot be guaranteed.
- Can lead to circular dependencies, causing system crashes.
- Observers may receive unnecessary notifications, wasting resources.
The observer pattern is applied in many libraries and frameworks in C++, such as the signal and slot mechanism in Qt, which is an implementation of the observer pattern, and the message mapping mechanism in MFC, which adopts a similar idea. In modern application development, the observer pattern is an essential tool for building loosely coupled systems.