Observer Design Pattern in Modern C++ vishal chovatiya
Reading Time: 3 minutes

The Observer Design Pattern is a type of Behavioural Design Pattern that use to get information when certain events happen i.e. basically one component want information about something happening in the other component. And that can a lot of things like a field changes to a particular value or you want to information when the object does a particular thing, etc. Observer Design Pattern in Modern C++ enables you to create subscription mechanism to notify multiple objects about events that happen to the object they’re observing.

By the way, If you haven’t check out my other articles on Behavioural Design Patterns, then here is the list:

  1. Chain of responsibility
  2. Command
  3. Interpreter
  4. Iterator
  5. Mediator
  6. Memento
  7. Observer
  8. State
  9. Strategy
  10. Template Method
  11. Visitor

The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.

Note:

  • If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
  • All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don’t have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.

Intent

To get notifications when events happen.

  • The Observer Design Pattern split into two parts:
    1. observer i.e. object which gets a notification about something happening somewhere in the system.
    2. observable i.e. entity that’s actually generating these notifications or events.
  • You see this are the terminology I am using which may vary people-to-people & domain-to-domain, For example:
    1. event & subscriber
    2. signal & slot(Boost, Qt, etc.)
    3. broadcaster & listeners, etc.

Observer Design Pattern Example in C++

template<typename T>
struct Observer {
    virtual void field_changed(T& source, const string& field_name) = 0;
};

template<typename T>
struct Observable {
    void notify(T& source, const string& field_name) {
        for (auto observer: m_observers)
            observer->field_changed(source, field_name);
    }
    void subscribe(Observer<T>& observer) { m_observers.push_back(&observer); }
    void unsubscribe(Observer<T>& observer) {
        m_observers.erase(remove(m_observers.begin(), m_observers.end(), &observer), m_observers.end());
    }

private:
    vector<Observer<T>*>    m_observers;
};

struct Person : Observable<Person>{  // Observable <<<<-------------------------------------
    void set_age(uint8_t age) {
        auto old_can_vote = get_can_vote();
        this->m_age = age;
        notify(*this, "age");

        if (old_can_vote != get_can_vote()) notify(*this, "can_vote");
    }
    uint8_t get_age() const { return m_age; }
    bool get_can_vote() const { return m_age >= 16; }

private:
    uint8_t m_age{0};
};

struct TrafficAdministration : Observer<Person>{     // Observer <<<<-----------------------
    void field_changed(Person &source, const string& field_name) {
        if (field_name == "age") {
            if (source.get_age() < 17)
                cout << "Not old enough to drive!\n";
            else {
                cout << "Mature enough to drive!\n";
                source.unsubscribe(*this);
            }
        }
    }
};

int main() {
    Person p;
    TrafficAdministration ta;
    p.subscribe(ta);
    p.set_age(16);
    p.set_age(17);
    return EXIT_SUCCESS;
}
  • The observer is that thing which wants to monitor something. And the observable is the component that is to monitored. So, in the above case, our Person is observable and the observer is TrafficAdministration.
  • You can also augment above code for passing lambda as a subscriber rather than an object for a more functional approach.

Observer Design Pattern with Boost Signals

  • Now what I’m going to do is I’ll make a small digression. Because instead of showing you something that we’ve built ourselves what I want to show you is the observable implementation that comes with the Boost libraries.
#include <iostream>
#include <string>
#include <boost/signals2.hpp>
using namespace std;

template<typename T>
struct Observable {  
    void subscribe(const auto&& observer) { m_field_changed.connect(observer); }
    void unsubscribe(const auto&& observer) { m_field_changed.disconnect(observer); }
protected:
    boost::signals2::signal<void(T&, const string&)>  m_field_changed;
};

struct Person : Observable<Person> {  // Observable <<<<-------------------------------------
    void set_age(uint8_t age) {
        this->m_age = age;
        m_field_changed(*this, "age");
    }
    auto get_age() const { return m_age; }

private:
    uint32_t    m_age {0};
};

struct TrafficAdministration {                    // Observer <<<<-----------------------
    static void field_changed(Person &source, const string& field_name) {
        if (field_name == "age") {
            if (source.get_age() < 17)
                cout << "Not old enough to drive!\n";
            else {
                cout << "Mature enough to drive!\n";
                source.unsubscribe(TrafficAdministration::field_changed);
            }
        }
    }
};

int main() {
    Person p;
    p.subscribe(TrafficAdministration::field_changed);
    p.set_age(16);
    p.set_age(20);
    return EXIT_SUCCESS;
}
  • Mind it, I have used boost::signals2 as boost::signals are no longer being actively maintained. Due to boost::signals2, we can get rid of std/boost::bind, and can directly use lambda. You can check out a quick example of boost::signals2 if you want.

Benefits of Observer Design Pattern

  1. It supports the loose coupling between objects that interact with each other hence Open-Closed Principle will be intact. Above examples also satisfy the Single Responsibility Principle as Observer & Observable are two different templatized classes which can easily be reusable.
  2. It provides the flexibility of adding or removing observers at any time which is heavily use in event-driven programming.

Summary by FAQs

Use cases of Observer Design Pattern.

Usually, Observer Design Pattern employs when there is a one-to-many relationship between objects so that when one object changes state, all its dependents are notified and updated automatically. Typical use case area involves GUI libraries, Social media, RSS feeds, Email subscription, etc.

Difference between Observer & Mediator Design Pattern.

Observer Design Pattern works on the one-to-many relationship.
Mediator Design Pattern works on the many-to-many relationship.

Do you like it☝️? Get such articles directly into the inbox…!?