I'm using the Observer design pattern in order to manage events in the game I'm currently developing.
I based myself on the implementation demonstrated here but improved it in order to ease its use.
class Observer
{
public:
virtual ~Observer() {}
virtual void onNotify(Subject * entity, Event event) = 0;
private:
};
class Subject
{
public:
Subject() {}
virtual ~Subject() {}
void addObserver(Observer* observer)
{
if (std::find(_observers.begin(), _observers.end(), observer) == _observers.end())
{
_observers.push_back(observer);
}
}
void removeObserver(Observer* observer)
{
std::list<Observer*>::iterator it = std::find(_observers.begin(), _observers.end(), observer);
if (it != _observers.end())
{
*it = NULL;
_eraseQueue.push(it);
}
}
protected:
// I'm passing the Subject, because it allows me to trigger an event
// on a newly created object, from another class :
// this->notify(new MyClass(...), MYCLASS_CREATED);
// It allows me to generate new entities easily
void notify(Subject * entity, Event event)
{
for (std::list<Observer*>::iterator it = _observers.begin(); it != _observers.end(); ++it)
{
if (*it != NULL)
(*it)->onNotify(entity, event);
}
while (!_eraseQueue.empty())
{
_observers.erase(_eraseQueue.front());
_eraseQueue.pop();
}
}
void notify(Subject * entity, Event event, Observer* observer)
{
if (observer != NULL)
observer->onNotify(entity, event);
}
private:
std::list<Observer*> _observers;
std::queue<std::list<Observer*>::iterator> _eraseQueue;
};
template <typename T>
class EventHandler : public Observer
{
public:
virtual ~EventHandler() {}
virtual void onNotify(Subject * entity, Event event)
{
if (dynamic_cast<T*>(this))
{
auto it = _actions.find(event);
if (it != _actions.end())
{
(dynamic_cast<T*>(this)->*(it->second))(entity);
}
}
}
protected:
template <typename U>
U safe_cast(Subject* entity)
{
if (dynamic_cast<U>(entity))
return (dynamic_cast<U>(entity));
else
throw std::exception("Event thrown on not-matching entity");
}
protected:
std::map<const Event, void (T::*)(Subject *)> _actions;
};
And here's some explanations about what makes it special:
- When removing an element from the observers list of a
Subject
, I'm storing the observers to remove into an erase queue, so that the iterator in thenotify
method still works even when removing an observer inside of thenotify
call.- "Observers" don't inherit from
Observer
directly, but inside they inherit fromEventHandler
which is a templated class that allows me to automatically call the corresponding pointers to function when receiving an event, without having to manage it in everyObserver
's daugther classes. - The
EventHandler
class implements asafe_cast
method so that I can convert theSubject
received fromonNotify
can be easily and safely casted to their inherited class.
- "Observers" don't inherit from
So, maybe you wonder why I'm using a EventHandler
class instead of inheriting directly from the Observer
class, but here's a quick example to show you why it's useful and makes the code much easier to read:
MyClass::MyClass()
{
_actions[GAME_STARTED] = &MyClass::gameStarted;
_actions[CHARACTER_MOVED] = &MyClass::characterMoved;
}
void MyClass::gameStarted(Subject * entity)
{
Game* game = safe_cast<Game*>(entity);
// Do actions on the Game instance
}
void MyClass::characterMoved(Subject * entity)
{
Character* character = safe_cast<Character*>(entity);
// Do actions on the Character instance
}
That's it. My event handling is completely hidden from the final user, he just has to store the events that he wants to listen in his class, and it will automatically be handled by the EventHandler
class.
I already know a few ways of improving my implementation of the Observer pattern:
- Using smart pointers,
- Actually, using references instead of pointers,
- Handle the case where the
Subject
instance gets deleted during thenotify
method call.
To clarify the role of the EventHandler class, here's how I inherit from it:
class MyClass : public EventHandler<MyClass>
Then, some explanations about the onNotify
method (the safe_cast
method is not really the main purpose of this class and seems to be pretty clear) :
virtual void onNotify(Subject * entity, Event event)
{
// Verify that the 'this' pointer is of type T
if (dynamic_cast<T*>(this))
{
// Find the pointer to function corresponding to the 'event' Event if it has been registered
auto it = _actions.find(event);
// Check if the pointer to function has been found
if (it != _actions.end())
{
// Calls the method on the dynamically casted 'this' pointer, so that it can
// generically call private methods on any class inheriting from EventHandler
(dynamic_cast<T*>(this)->*(it->second))(entity);
}
}
}
And an example of how I'm using this:
// MyClass is inheriting from a 'self-templated' EventHandler
class MyClass : public EventHandler<MyClass>
{
public:
MyClass() {
// I'm registering in the EventHandler _actions tab the
// events that this particular class is listening to
_actions[CHARACTER_MOVED] = &MyClass::characterMoved;
_actions[CHARACTER_DIED] = &MyClass::characterDied;
// Some other initialization
};
virtual ~MyClass(){}
private:
void characterMoved(Subject* entity) {
// I'm converting the generic Subject object to a Character object
Character* character = safe_cast<Character*>(entity);
// I'm doing whatever I want with the character that triggered the event
std::cout << character->getName() << " moved !" << std::endl;
}
void characterDied(Subject* entity) {
// I'm converting the generic Subject object to a Character object
Character* character = safe_cast<Character*>(entity);
// I'm doing whatever I want with the character that triggered the event
std::cout << "Oh no ! " << character->getName() << " died ! RIP" << std::endl;
delete character;
}
}
This saves me from implementing the onNotify
method in every single class inheriting from Observer
, and it's making the handling of the events completely hidden from the end user, that only has to register the events its class is listening to in its constructor.
But I'd like to have your opinion about the other things that I could improve, and your overall feel about this implementation.