Published Oct 17, 2019
[
 
]
Observer pattern lets one piece of code announce that something interesting happened without actually caring who receives the notification.
To implement the “Fall off a Bridge” badge
void Physics::updateEntity(Entity& entity)
{
bool wasOnSurface = entity.isOnSurface();
entity.accelerate(GRAVITY);
entity.update();
if (wasOnSurface && !entity.isOnSurface())
{
notify(entity, EVENT_START_FALL);
}
All it does is say, “Uh, I don’t know if anyone cares, but this thing just fell. Do with that as you will.”
The physics engine does have to decide what notification to send, so it isn’t entirely decoupled. But in architecture, we’re most often trying to make systems better, not perfect.
The achievement system register itself so that whenever the physics code sends a notification, the achievement system receives it.
class Observer
{
public:
virtual ~Observer() {}
virtual void onNotify(const Entity& entity, Event event) = 0;
};
The parameters to onNotify()
are up to you. Typical parameters are the object that sent
the notification and a generic “data” parameter you stull other details into.
The achievement system
class Achievements : public Observer
{
public:
virtual void onNotify(const Entity& entity, Event event)
{
switch (event)
{
case EVENT_ENTITY_FELL:
if (entity.isHero() && heroIsOnBridge_)
{
unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);
}
break;
// Handle other events, and update heroIsOnBridge_...
}
}
private:
void unlock(Achievement achievement)
{
// Unlock if not already unlocked...
}
bool heroIsOnBridge_;
};
The notification method is invoked by the object being observed. In Gang of Four parlance, that object is called the “subject”. It has two jobs. First, it holds the list of observers that are waiting for a missive from it:
class Subject
{
private:
Observer* observers_[MAX_OBSERVERS];
int numObservers_;
};
The important bit is that the subject exposes a public API from modifying that list:
class Subject
{
public:
void addObserver(Observer* observer)
{
// Add to array...
}
void removeObserver(Observer* observer)
{
// Remove from array...
}
// Other stuff...
};
The other job of the subject is sending notification:
class Subject
{
protected:
void notify(const Entity& entity, Event event)
{
for (int i = 0; i < numObservers_; i++)
{
observers_[i]->onNotify(entity, event);
}
}
// Other stuff...
};
Note that this code assumes observers don’t modify the list in their onNotify()
methods.
A more robust implementation would either prevent or gracefully handle concurrent
modification like that.
Now, we just need to hook all of this into the physics engine so that it can send notifications and the achievement system can wire itself up to receive them.
class Physics : public Subject
{
public:
void updateEntity(Entity& entity);
};
In real code, I would avoid using inheritance here. Instead, I’d make Physics
have an
instance of Subject
. Instead of observing the physics engine itself, the subject would
be a separate “falling event” object. Observers could register themselves using something
like:
physics.entityFell()
.addObserver(this);
To me, this is the difference between “observer” systems and “event” systems. With the former, you observer the thing what did something interesting. With the latter, you observe an object that represents “the interesting thing that happened”.
References: