Yesterday I was trying to implement the
observer pattern for use in a project of mine. The classes needed to be generic, type-safe, fast and easy to use. Asking too much ? Well no, not in the realm of C++ :) To retain type-safety I had to incorporate
static polymorphism . That means
virtual functions were to be dumped for good... They force you to make dangerous type casts causing some nausea :) . This was the first thing that I typed into the editor without thinking :)
// A first and naive shot at the problem
#include < list>
#include < boost/bind.hpp>
#include < boost/function.hpp>
#include < boost/foreach.hpp>
template < typename TObserver, typename TSubject>
class observer
{
public:
void observe(TSubject& s)
{
static_cast< TObserver *>(this)-> do_observe(s);
}
};
template < typename TObserver, typename TSubject>
class subject
{
public:
typedef observer< TObserver, TSubject> observer_type;
private:
std::list< observer_type *> observers;
public:
typedef observer< TObserver, TSubject> observer_type;
void add_observer(observer_type *po)
{ observers.push_back(po); }
void remove_observer(observer_type *po)
{ observers.remove(po); }
void notify_all()
{
BOOST_FOREACH(observer_type *po, observers)
{
po-> observe(*static_cast< TSubject *>(this));
}
}
};
As with all "typing without thinking" practices, this piece of code yields a disappointment :) The idea is that your "concrete observer" will derive from the
observer class. So it will be parameterize with
its own type and the
subject type . The
subject template also needs - two - types to resolve the function call
observe() . But this means for each type of
observer you must define a new type of
concrete subject.
e.g.
// Forward declaration
class model;
class view : public observer< view, model>
{
public:
void do_observe(model& m)
{
std::cout < < "view observed the model." < < std::endl;
}
};
class controller : public observer< controller, model>
{
public:
void do_observe(model& m)
{
std::cout << "controller observed the model." < < std::endl;
}
};
// Here comes the problem..
class model : public subject< view /* oh oh !? */, model>
{
};
See the declaration of
class model ? With this declaration a "model" object can only accept a "view" object as an observer.
After experimenting a bit, I was convinced that this is a bit hard to solve (at least with my current knowledge). The main problem is that your
concrete subject can notify only a single type of observer. If we were to use virtual functions, there would be no limitation on what type of
observer we could use with the subject as long as our
concrete observer were derived from the
abstract observer. But we don't want it because we want type-safety and performance ;). So how to achieve it ? Let's summarize everything :)
Subjects can only notify a single type of observer. Moreover observers and subjects must be strongly typed so the programmer won't be able to register an observer operating on apples with a subject representing an orange :)
My point of view is that I should encapsulate the -operation- while satisfying the above requirements. The solution I came up with is to keep a list of
function objects representing the
operation - observe(). So here is the code for the pattern;
// observer.hpp
#ifndef OBSERVER_HPP
#define OBSERVER_HPP
#include < list>
#include < boost/bind.hpp>
#include < boost/function.hpp>
#include < boost/foreach.hpp>
// Forward declaration for subject
template < typename TSubject>
class subject;
template < typename TObserver, typename TSubject>
class observer
{
public:
void observe(TSubject& s)
{
static_cast< TObserver *>(this)-> do_observe(s);
}
};
template < typename TSubject>
class subject
{
public:
typedef boost::function< void (TSubject& ) > observer_type;
private:
std::list< observer_type > observers;
public:
template < typename TObserver>
void add_observer(observer< TObserver, TSubject>& o)
{
observer_type o_fn = boost::bind(&observer< TObserver, TSubject>::observe, boost::ref(o), _1);
observers.push_back(o_fn);
}
protected:
void notify_all()
{
BOOST_FOREACH(observer_type o, observers)
{
o(boost::ref(*static_cast< TSubject *>(this)));
}
}
};
#endif
STL and boost creates miracles for sure :) Let's have a look at the semantics of these templates. A
concrete subject will derive from the
subject class and when it's modified will call the
notify_all() function internally. A
concrete observer needs to derive from the
observer class and must define a
public do_observe() function which will operate on the
concrete subject. Let's see the example client code;
A simple implementation of MVC pattern with our observer templates. (I promised for one pattern and you get two ! Yay ! )
// main.cpp
#include < iostream>
#include "observer.hpp"
class model : public subject< model>
{
public:
model()
{
std::cout << "model constructed\n";
}
void modify_model()
{
this-> notify_all();
}
};
class view : public observer< view, model>, public subject< view>
{
public:
void do_observe(model& m)
{
std::cout << "view observed the model" < < std::endl;
}
};
class controller : public observer< controller, model>, public observer< controller, view>
{
public:
void do_observe(model& m)
{
std::cout << "controller observed the model" << std::endl;
}
void do_observe(view& v)
{
std::cout << "controller observed the view" << std::endl;
}
};
int main()
{
model m;
controller c;
view v;
m.add_observer(v);
m.add_observer(c);
v.add_observer(c);
// when the model is modified all its observers will be notified
m.modify_model();
return 0;
}
So tell me what you think. I'm eager to hear a better solution or any suggestions to improve this one. If there are any problems with the above code that I can't see please comment so we can discuss. Thanks in advance ! :)