Skip to content

Observer Pattern

Overview

The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When the subject's state changes, all its dependents (observers) are notified and updated automatically. This pattern is also known as the Publish-Subscribe pattern.

Intent

  • Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
  • Encapsulate the core (or common or engine) components in a Subject abstraction, and the variable (or optional or user interface) components in an Observer hierarchy
  • The "View" part of Model-View-Controller

Problem

You need to notify multiple objects about changes to another object's state without creating tight coupling between them. Hard-coding these dependencies results in inflexible code that's difficult to maintain and extend.

Solution

Define a subscription mechanism that allows objects to subscribe to and receive notifications from other objects. The object that has the interesting state is called the subject (or observable), and the objects that want to be notified are called observers (or listeners).

Structure

Class Diagram

Observer Pattern Diagram 0

Components

  1. Subject (Observable)

    • Maintains a list of observers
    • Provides methods to attach and detach observers
    • Notifies observers of state changes
  2. Observer (Listener)

    • Defines an interface for objects that should be notified of changes
    • Declares the update method
  3. ConcreteSubject

    • Stores state of interest to ConcreteObserver objects
    • Sends notifications to observers when state changes
    • Provides methods to get/set state
  4. ConcreteObserver

    • Maintains a reference to a ConcreteSubject object
    • Stores state that should stay consistent with the subject's
    • Implements the Observer update interface

Implementation Examples

Example 1: Weather Station

A weather monitoring system where multiple displays observe weather data changes.

Observer Pattern Diagram 1

Usage:

// Create weather data subject
WeatherData weatherData;

// Create observer displays
CurrentConditionsDisplay currentDisplay;
StatisticsDisplay statsDisplay;
ForecastDisplay forecastDisplay;

// Register observers
weatherData.attach(&currentDisplay);
weatherData.attach(&statsDisplay);
weatherData.attach(&forecastDisplay);

// Simulate weather changes - all displays update automatically
weatherData.setMeasurements(25.0f, 65.0f, 1013.0f);
weatherData.setMeasurements(27.0f, 70.0f, 1012.0f);

// Remove an observer
weatherData.detach(&statsDisplay);
weatherData.setMeasurements(23.0f, 60.0f, 1014.0f);

Example 2: Stock Market

Stock prices notify multiple investors when they change.

Observer Pattern Diagram 2

Usage:

Stock apple("AAPL", 150.0);
Stock google("GOOGL", 2800.0);

Investor alice("Alice");
Investor bob("Bob");

apple.attach(&alice);
apple.attach(&bob);
google.attach(&alice);

// Price changes notify investors
apple.setPrice(155.0);   // Alice and Bob notified
google.setPrice(2850.0); // Only Alice notified

Example 3: Event-Driven System

Generic event manager with multiple listener types.

Observer Pattern Diagram 3

Usage:

EventManager events;

LoggingListener logger;
EmailAlerter emailer;
SMSAlerter sms;

// Subscribe to different events
events.subscribe("file:save", &logger);
events.subscribe("file:save", &emailer);
events.subscribe("error", &logger);
events.subscribe("error", &sms);

// Trigger events
events.notify("file:save", "document.txt saved");
events.notify("error", "Database connection failed");

Example 4: Push vs Pull Model

Push Model: Subject sends specific data to observers

class PushSubject : public Subject {
    int state_;
public:
    void setState(int state) {
        state_ = state;
        notify();  // Observers receive state directly
    }

    int getState() const { return state_; }
};

class PushObserver : public Observer {
    void update(Subject* subject) override {
        auto pushSubject = dynamic_cast<PushSubject*>(subject);
        if (pushSubject) {
            // Subject pushes the state
            int state = pushSubject->getState();
            // Use state...
        }
    }
};

Pull Model: Observers query subject for needed data

class PullSubject : public Subject {
    int state_;
    std::string description_;
public:
    void setState(int state, const std::string& desc) {
        state_ = state;
        description_ = desc;
        notify();  // Observers pull what they need
    }

    int getState() const { return state_; }
    std::string getDescription() const { return description_; }
};

class PullObserver : public Observer {
    void update(Subject* subject) override {
        auto pullSubject = dynamic_cast<PullSubject*>(subject);
        if (pullSubject) {
            // Observer pulls only what it needs
            if (needsState) {
                int state = pullSubject->getState();
            }
            if (needsDescription) {
                std::string desc = pullSubject->getDescription();
            }
        }
    }
};

Sequence Diagram

Observer Pattern Diagram 4

Real-World Applications

1. GUI Event Handling

Multiple UI components observe user interactions:

// Button click events
button.onClick([](){ /* handle click */ });

// Window resize events
window.onResize([](int width, int height){ 
    /* adjust layout */ 
});

// Mouse and keyboard events
canvas.onMouseMove([](int x, int y){ 
    /* update cursor */ 
});

2. Model-View-Controller (MVC)

The Model notifies Views when data changes:

1
2
3
4
5
6
Model (Subject)
  ↓ notify
Views (Observers)
  - TableView
  - ChartView
  - SummaryView

All views automatically update when the model changes.

3. Social Media Notifications

1
2
3
4
5
6
7
8
User (Subject)
  - Post new content
  - Update status

Followers (Observers)
  - Receive notifications
  - Update feeds
  - Get alerts

4. Stock Market Systems

1
2
3
4
5
6
7
8
9
Stock Price (Subject)
  - Price changes
  - Volume changes

Interested Parties (Observers)
  - Investors
  - Trading platforms
  - Alert systems
  - News services

5. RSS/News Feeds

1
2
3
4
5
6
7
8
Content Publisher (Subject)
  - New articles
  - Updates

Subscribers (Observers)
  - Feed readers
  - Email notifications
  - Mobile apps

6. Logging Systems

Application Events (Subject)
  - Errors
  - Warnings
  - Info messages

Loggers (Observers)
  - File logger
  - Console logger
  - Remote logger
  - Database logger

7. Pub/Sub Messaging

Modern message brokers like Kafka, RabbitMQ:

1
2
3
4
5
6
7
Topic (Subject)
  - Messages published

Subscribers (Observers)
  - Multiple services
  - Different teams
  - Various systems

Design Considerations

Advantages

Loose Coupling: Subject and observers are loosely coupled. Subject knows only that observers implement the Observer interface.

Open/Closed Principle: You can introduce new observer classes without changing the subject's code.

Dynamic Relationships: You can establish relationships between objects at runtime.

Broadcast Communication: Subject doesn't need to know who or how many observers there are.

Reusability: Observers can be reused with different subjects.

Disadvantages

Unexpected Updates: Observers don't know about each other and may trigger cascading updates.

Memory Leaks: If observers don't unsubscribe, they can cause memory leaks.

Performance Overhead: Notifying many observers can be expensive.

Undefined Update Order: The order in which observers are notified is typically undefined.

Debugging Complexity: Tracking updates through multiple observers can be difficult.

Push vs Pull Trade-offs

Push Model: - ✅ Less coupling (observers don't query subject) - ✅ Simpler observer implementation - ❌ Subject must know what data observers need - ❌ Less flexible (all observers get same data)

Pull Model: - ✅ More flexible (observers get only what they need) - ✅ Subject doesn't need to know observer requirements - ❌ More coupling (observers must know subject interface) - ❌ More complex observer implementation

When to Use

Use Observer Pattern when:

  • One object's state change requires changing other objects
  • You don't know how many objects need to be changed
  • An object should be able to notify other objects without making assumptions about who they are
  • You need to decouple abstraction from its implementation

Avoid Observer Pattern when:

  • You have simple one-to-one relationships
  • Performance is critical and you have many observers
  • Observers have complex dependencies on each other
  • The cost of maintaining the observer list outweighs the benefits

Best Practices

  1. Prevent Memory Leaks: Use weak pointers or ensure observers properly unsubscribe
class Subject {
    std::vector<std::weak_ptr<Observer>> observers_;  // Use weak_ptr

    void notify() {
        // Remove expired observers
        observers_.erase(
            std::remove_if(observers_.begin(), observers_.end(),
                [](const auto& wp) { return wp.expired(); }),
            observers_.end()
        );

        // Notify remaining observers
        for (auto& wp : observers_) {
            if (auto sp = wp.lock()) {
                sp->update(this);
            }
        }
    }
};
  1. Thread Safety: Protect observer list in multi-threaded environments
class ThreadSafeSubject {
    std::mutex mutex_;
    std::vector<Observer*> observers_;

    void notify() {
        std::lock_guard<std::mutex> lock(mutex_);
        for (auto observer : observers_) {
            observer->update(this);
        }
    }
};
  1. Avoid Cascading Updates: Prevent observers from modifying the subject during notification
class Subject {
    bool notifying_ = false;

    void notify() {
        notifying_ = true;
        // ... notify observers ...
        notifying_ = false;
    }

    void setState(State s) {
        if (notifying_) {
            throw std::runtime_error("Cannot modify during notification");
        }
        state_ = s;
        notify();
    }
};
  1. Document Notification Order: If order matters, document it clearly
1
2
3
4
5
6
7
8
9
class OrderedSubject {
    std::vector<std::pair<int, Observer*>> observers_;  // priority, observer

    void notify() {
        // Sort by priority before notifying
        std::sort(observers_.begin(), observers_.end());
        // ...
    }
};
  1. Consider Event Filtering: Allow observers to filter which events they receive
1
2
3
4
5
6
7
8
9
class SelectiveObserver : public Observer {
    std::set<EventType> interestedEvents_;

    void update(Subject* subject, EventType event) override {
        if (interestedEvents_.count(event)) {
            // Handle event
        }
    }
};
  • Mediator: Defines a centralized communication hub, while Observer distributes communication
  • Singleton: Subjects are often implemented as singletons
  • Command: Can use Observer to notify about command execution
  • Chain of Responsibility: Can be combined with Observer for event handling

Summary

The Observer Pattern provides an elegant solution for implementing distributed event handling systems. It promotes loose coupling and allows you to add or remove observers at runtime. However, it requires careful consideration of memory management, thread safety, and potential cascading update issues. When implemented correctly, it becomes a powerful tool for building responsive, event-driven applications.