ObserverPattern
ObserverPattern Documentation

Introduction

This is a C++ implementation of the observer pattern as you may know it from Java listeners. Thanks to C++ template magic, client implementation of an observer source class is very simple and requires almost no coding at all.

Download

The source code of this library is available on GitHub. The code is licenced under MIT licence.

Possible implementation

A naive implementation of the pattern require the developer to add three methods and one container for each listener. Say we have a MouseListener interface, which listens to mouse events and MouseSource class, which emits those events. The implementation of the MouseListener is straightforward:

class MouseListener {
public:
virtual ~MouseListener() {}
virtual void onLeftMouseButtonUp(int x, int y) {}
virtual void onLeftMouseButtonDown(int x, int y) {}
virtual void onRightMouseButtonUp(int x, int y) {}
// ...
};

The implementation of the source class, however, requires some manual work:

class MouseSource {
public:
// ...
void attachMouseListener(MouseListener* listener);
void detachMouseListener(MouseListener* listener);
protected:
void notifyLeftMouseButtonUp(int x, int y);
void notifyLeftMouseButtonDown(int x, int y);
// ...
private:
std::vector<MouseListener*> m_mouseListeners;
};

As you can see, there is a lot of repetitive manual work to implement all the notify functions as well as attach and detach methods. When we later decide that the source should be actually source of keyboard event as well, things get much worse. Now the class would have to keep two lists of listeners, two pairs of attach and detach methods and number of notify functions, all of them iterating listeners and calling correct listener function.

Implementation using the Observer namespace

If one uses the provided Observer library, the implementation of the MouseListener interface doesn't have to change at all. The Observer library makes the source implementation is much simpler though. The concrete mouse listener implementation must inherit from Observer::Listener and not from the listener directly as shown in the following example.

class ConcreteMouseListener : public Observer::Listener<MouseListener> {
public:
void onLeftMouseButtonUp(int x, int y) override {
// ...
}
};
class MouseSource : public Observer::RawSource<MouseListener> {
public:
void f() {
int x, y;
// ...
notify(&MouseListener::onLeftMouseButtonUp, x, y);
// ...
}
};
void test() {
ConcreteMouseListener listener;
MouseSource source;
source.attach(&listener); // attach the listener
source.f(); // call f() method, which emits onLeftMouseButtonUp notification
source.detach(&listener); // detach the listener
}

As you can see the source implementation doesn't have to define attach, detach, or notify* functions. They are all implemented in the Observer::RawSource class. RawSource denotes that raw pointers are used for storing the listenres. If you prefer smart pointers instead, please use Observer::SmartSource.

Support of mutiple listeners

The aforementioned example is already an improvement but what if the source should support multiple listeners, or the concrete listener is composed of multiple abstract listeners? This is not a problem for the Observer library and it supports both scenarios. Consider the following example:

class ConcreteListener : public Observer::Listener<ListenerA, ListenerB> {
public:
// ...
};
class Source : public Observer::RawSource<ListenerB, ListenerC> {
public:
// ...
};
void test() {
ConcreteListener listener;
Source source;
// Attach the listener. Only abstract ListenerB is actually attached to the source
// as ListenerA is not supported by the source and ListenerC is not implemented in
// in the concrete listener.
source.attach(&listener);
//...
}

Say we have three abstract listeners ListenerA, ListenerB, and ListenerC. The concrete listener implemens only ListenerA and ListenerB, whereas the source implementation supports ListenerB and ListenerC. Clearly, the only abstract listener supported by the concrete listener and the source is ListenerB. Still, the user may attach the concrete listener to the source without a compiler error and only the correct abstract interfaces are taken into account and attached to the source as one would expect.