Observer Design Pattern in C++

The Observer Design Pattern is one of the most widely used behavioral patterns in software development. It provides a mechanism to establish a one-to-many relationship between objects, ensuring that when the state of one object (the Subject) changes, all dependent objects (the Observers) are automatically notified. This pattern is particularly useful in scenarios where changes in one part of an application must trigger updates elsewhere.

Key Concepts

  1. Observer Interface: Defines a common interface for all observers, typically with an update() method that is called by the subject to notify the observer of any state changes.
  2. Concrete Observer: Implements the Observer interface and defines specific behavior in response to notifications from the subject.
  3. Subject Interface: Declares methods to manage observers, including adding, removing, and notifying them.
  4. Concrete Subject: Implements the subject interface, maintains a list of observers, and triggers updates to them when its state changes.

Implementation in C++

Observer Interface

The Observer class provides an interface for all observers. It ensures that any class implementing it will define the update() method. This method is called by the subject to notify observers of any state changes.

// Observer interface
class Observer {
public:
    virtual void update(const std::string &message) = 0;
    virtual ~Observer() = default;
};

Concrete Observer

The ConcreteObserver class implements the Observer interface and defines specific behavior in its update() method. Each observer is identified by a unique name

// Concrete Observer
class ConcreteObserver : public Observer {
private:
    std::string name;
public:
    explicit ConcreteObserver(const std::string &name) : name(name) {}
    void update(const std::string &message) override {
        std::cout << "Observer [" << name << "] received message: " << message << std::endl;
    }
};

Subject Interface

The Subject interface declares methods for managing observers and notifying them. This abstraction decouples the subject from its concrete implementation

// Subject interface
class Subject {
public:
    virtual void attach(std::shared_ptr<Observer> observer) = 0;
    virtual void detach(std::shared_ptr<Observer> observer) = 0;
    virtual void notify(const std::string &message) = 0;
    virtual ~Subject() = default;
};

Concrete Subject

The ConcreteSubject class maintains a list of observers and implements methods for managing and notifying them. Notifications are sent to all observers using the notify() method:

// Concrete Subject
class ConcreteSubject : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers;
public:
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
    }

    void detach(std::shared_ptr<Observer> observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void notify(const std::string &message) override {
        for (const auto &observer : observers) {
            observer->update(message);
        }
    }
};
int main() {
    auto subject = std::make_shared<ConcreteSubject>();

    auto observer1 = std::make_shared<ConcreteObserver>("Observer1");
    auto observer2 = std::make_shared<ConcreteObserver>("Observer2");

    subject->attach(observer1);
    subject->attach(observer2);

    subject->notify("Event 1");

    subject->detach(observer1);

    subject->notify("Event 2");

    return 0;
}

Advantages of the Observer Pattern

  1. Decoupling: Subjects and observers are loosely coupled, adhering to the Open/Closed Principle.
  2. Scalability: New observers can be added or removed without modifying the subject’s code.
  3. Dynamic Updates: Observers can dynamically subscribe or unsubscribe from the subject, making the system flexible.

Practical Applications

  • Event Systems: GUI frameworks use this pattern for event handling.
  • Real-Time Systems: Notifications in stock trading platforms or weather monitoring systems.
  • Logging Systems: Applications that need to send logs to multiple output streams.