I have been playing around with ways of implementing a generic class for supporting the observer pattern. I have already asked to review a basic approach here. Now I have come up with an Observable class that supports parametrizing with an arbitrary number of function signatures, in order for the subject class to register and notify observers interested in different kinds of events that require different kind of information to be transmitted.
Here is the code:
observable.h:
#include <functional>
#include <vector>
#include <tuple>
template<typename ... Funcs>
class Observable
{
public:
using ObserverList = std::tuple<std::vector<std::function<Funcs>>...>;
template<unsigned int eventidx, typename Callable>
void AttachObserver(Callable c)
{
std::get<eventidx>(observers).push_back(c);
}
template<unsigned int eventidx, typename ... T>
void NotifyObserversOfEvent(T ... args)
{
for(auto f: std::get<eventidx>(observers))
{
f(std::forward<T>(args)...);
}
}
private:
ObserverList observers;
};
Sample Test code
main.cpp:
#include <iostream>
#include <functional>
#include <vector>
#include "observable.h"
class A : public Observable<
void(void),
void(int,std::string),
void(int,int),
>
{
public:
enum Event : unsigned
{
Event0,
Event1,
Event2
};
private:
int a;
std::string s;
};
class B
{
public:
void EventZeroHappened()
{
std::cout << "B has observed event zero\n";
}
void EventOneHappened(int a, std::string s)
{
std::cout << "B has observed event one with params: " << a << " and " << "\"" << s << "\"" <<'\n';
}
void EventTwoHappened(int a, int b)
{
std::cout << "B has observed event two with params: " << a << " and " << b <<'\n';
}
};
int main()
{
A a;
B b;
a.AttachObserver<A::Event::Event0>([&b](){b.EventZeroHappened();});
a.AttachObserver<A::Event::Event1>([&b](int i, std::string s){b.EventOneHappened(i,s);});
a.AttachObserver<A::Event::Event2>([&b](int i, int j){b.EventTwoHappened(i,j);});
a.NotifyObserversOfEvent<A::Event::Event0>();
a.NotifyObserversOfEvent<A::Event::Event1>(37,"Hello There");
a.NotifyObserversOfEvent<A::Event::Event2>(182,150);
}
Basically the idea I wanted to implement is that the user of the observable class, simply writes in the template parameter list, the function signatures of the callback it supports. Instead of forcing the observer to implement an abstract interface and derive from it, which pollutes the observer code and creates an unnecessary dependency. The only draw back is that in order to select which event the observer wants to subscribe to (and which event the subject wants to transmit) it has to be done via integral constant, which is made a bit nicer in the example with an enum. I'm still searching for a better way to do this part.
The reason behind that is that, even in the old way of implementing observer, where you hard-code in a *.h file a pure abstract class and tell other classes that want to observe: "please overload this virtual functions if you want to observe me". And the meaning of each of the events you are going to notify is encoded in the names of the functions. I thought this example was a more general and flexible way of transmitting the same information. Plus being a bit easier to modify to add new events.
As always, I would welcome any comments, corrections criticisms and suggestions.