Contents

My favourite design pattern: The Observer

Why Observer is so cool?

Managing complexity in large code base is all about decoupling. Observer is a perfect tool to do that. The Observed object has no idea about its observers, no dependency injection is required at all, as a result a loose coupling is achieved between two classes of objects where the only common part is the interface they agree upon.

First iteration

It’s a classic design pattern and I’m sure majority (if not all) of engineers know the details behind it, most of us implement it in a slightly different way, depending on the requirements and the code base we are working with though. Here’s my flavour of this design pattern.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Observer {
public:
    virtual ~Observer() = default;

    virtual void update() = 0;
};

class Observed {
public:
    virtual ~Observed() = default;

    void addObserver(std::shared_ptr<Observer> observer) final {
        observers.push_back(observer);
    }

    void removeObserver(std::shared_ptr<Observer> observer) final {
        observers.erase(std::remove_if(observers.begin(), observers.end(),
            [observer](const auto o){ return o.lock() == observer; }),
            observers.end());
    }

    void notifyObservers() final {
        for (const auto o : observers) {
            if (auto strong = o.lock()) {
                strong->update();
            }
        }
    }

private:
    std::vector<std::weak_ptr<Observer>> observers;
};

This implementation would be used in a following way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class ConcreteObserved : public Observed {
public:
};

class ConcreteObserver : public Observer {
public:
    void update() override {
        std::cout << "Received an update" << std::endl;
    }
};

...
ConcreteObserved observed;
auto concreteObserver = std::make_shared<ConcreteObserver>();

observed.addObserver(concreteObserver);
observed.notify();

That’s how the fundamentals usually look. The Observed object generates update() events to notify its observers that something has happened. There’s a couple of problems with this code though, preventing it from being a generic, library implementation that could be used independently. Let’s address these.

Generic Observer type

First of all, the implementation dictates the API for Observers. Each Observer can only be updated using a single update function which doesn’t take any arguments. It’s impossible to pass any extra data along with the notification. One could argue that you could change the signature to i.e.:

1
void update(std::string details);

But that is not really a good solution. What if I want a different type than std::string?

Often, you’ll want to receive updates of more than one type. Additionally, you’ll most likely want to get some information passed in update() as an argument to understand what has changed within the Observed object. To conclude, the Observer can’t be defined upfront - it has to be a template parameter.

1
2
3
4
5
6
7
template <typename ObserverT>
class Observed {
    ...
    void notifyObservers() {
        // how to implement this?
    }
}

Since the Observer’s API is unknown up front it’s impossible to implement the notification functions in a generic way. This could be solved in a simple manner by delegating the problem to the clients:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class ClientObserver {
public:
    virtual void updateA(int a) = 0;

    virtual void updateB(std::string b) = 0;
};

class ClientConcreteObserver : public ClientObserver {
public:
    void updateA(int a) {
        std::cout << "got update A with value: " << a << std::endl;
    }

    void updateB(std::string b) {
        std::cout << "got update B with value: " << b << std::endl;
    }
};

class ClientObserved : public Observed<ClientObserver> {
    ...
    void notifyA(int i) {
        for (const auto o : observers) {
            if (auto strong = o.lock()) {
                strong->updateA(i);
            }
        }
    }

    void notifyB(std::string s) {
        for (const auto o : observers) {
            if (auto strong = o.lock()) {
                strong->updateB(s);
            }
        }
    }
};

In the code above, the client defines, the Observer interface (which it then implements as well). Having an abstract observer interface, allows for creation of Observed instances with that particular type.

Sure, this will work but it leads to a terrible amount of code duplication. Each notify function is roughly duplicating the same pattern but with a slightly different update call. This problem can be solved in two ways.

Pointer to members

Firstly, I’m gonna generalise the implementation of the notify function. It’ll now be a function template and it’ll look like so:

1
2
3
4
5
6
7
8
    template <typename ReturnT, typename ... ArgsT>
    void notify(ReturnT (ObserverT::*member)(ArgsT...), ArgsT&& ... args) {
        for (const auto o : observers) {
            if (auto strong = o.lock()) {
                (strong.get()->*member)(std::forward<ArgsT>(args)...);
            }
        }
    }

Granted, it looks a bit scary but after a minute it becomes clear. I’m using a pointer to member here and a variadic template to handle function arguments which are perfectly forwarded. How to use it?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ClientObserved : public Observed<Observer> {
public:
};

class ClientConcreteObserver : public Observer {
public:
    void updateA(int a) {
        std::cout << "got update A with value: " << a << std::endl;
    }

    void updateB(std::string b) {
        std::cout << "got update B with value: " << b << std::endl;
    }
};

...
auto co = std::make_shared<ClientConcreteObserver>();
ClientObserved o;

o.addObserver(co);
o.notify(&Observer::updateA, 456);
o.notify(&Observer::updateB, std::string("hello"));

That looks good. The provided implementation is generic enough to exist independently and be reusable. Just for reference, I’m gonna show another way to implement notifications, using functors.

Functors

The implementation of notify member function becomes simpler and more concise:

1
2
3
4
5
6
7
8
    template <typename FunctorT>
    void notify(FunctorT functor) {
        for (const auto o : observers) {
            if (auto strong = o.lock()) {
                functor(strong.get());
            }
        }
    }

In here I expect to receive a functor taking a pointer to Observer* type explicitly. In other words, the function signature becomes as following:

1
    void functor(Observer* o, ...);

The complexity is shifted to the client side. In order to use the notify function in its current form, functors have to be created in place:

1
2
3
4
5
6
7
8
9
    using namespace std::placeholders;
    ...

    auto co = std::make_shared<ClientConcreteObserver>();
    ClientObserved o;

    o.addObserver(co);
    o.notify(std::bind(&Observer::updateA, _1, 456));
    o.notify(std::bind(&Observer::updateB, _1, std::string("hello")));

All details and a more concise reference can be found on gitlab.