There’s been some interesting discussion about the merits of the Observer design pattern recently and I thought that it would be a good idea to give a simplified explanation of what this pattern is and how it works for those who haven’t read the infamous “Gang of Four’s” book. We’ll use Xojo as the language for the explanation.
I’m working on a game engine at the moment, specifically the physics engine. Let’s say that in my game I want to support trophies (i.e: achievements) so that the player can be rewarded when they, well, achieve certain goals. One such trophy is “Go Stratospheric”. This’ll be awarded to the player whenever their acceleration exceeds a certain threshold value.
The thing about trophies is that you get given them for all sorts of things, not just events that can be detected by the physics engine. For example, you might get a trophy the first time you complete a level, etc. If we start hard-coding conditions into the game for when to award a trophy to the player we’ll find that our trophy code spreads like a cancer through our game. Sure “going stratospheric” does have something to do with our physics engine but it seems dirty to have an awardStratosphericTrophy()
method call in the middle of the impulse resolution code of our physics engine .
Like all good programmers, we need to organise code to handle these kind of scenarios. This is a perfect example of where we can use The Observer Pattern.
Let’s take my example of giving a trophy to the player if their acceleration exceeds the REALLY_FAST
threshold. Here’s a pseudo-snippet from the physics engine update code:
Sub UpdatePhysicsBody(b As Body)
b.Velocity = b.Velocity + someVelocity
If b.Velocity > REALLY_FAST Then
Notify(b, GOING_STRATOSPHERIC)
End If
End Sub
All this code is saying is “If anyone cares out there, this particular Body
in the simulation has just gone really fast”. With the Observer pattern our trophy system will register itself such that whenever the physics engine sends a notification, the trophy system receives it. The trophy system can then decide if the Body
that is going really fast is the player. If it was, it’ll award the Going Stratospheric trophy to the player. All this is achieved without the physics engine giving two hoots about unlocks and trophies - all it cares about is vector maths, like a good little engine. In actual fact, if we decide later that we don’t want a trophy system anymore and we rip it out of our game, the physics engine doesn’t need to be altered at all.
Implementation
The implementation is really easy, it needs two participants: the Observer
and the Subject
.
The Observer
Any object that wants to know when something interesting happens to another object must implement our Observer
interface.
Interface Observer
Sub NotificationReceived(obj As Variant, theEvent As Integer)
End Interface
The Observer
class interface has just one method that classes that use it must implement: NotificationReceived()
. This takes two arguments: the object the notification relates to and an integer representing the type of event that occurred. You don’t have to use an integer (you could use a String
or an Enumeration
) but this will suffice for now.
Any class that implements the Observer
interface becomes an observer. In this example, that’s our trophy system so let’s define a simple class to represent it:
Class TrophySystem Implements Observer
Sub NotificationReceived(obj As Variant, theEvent As Integer)
Select Case theEvent
Case GOING_STRATOSPHERIC
// Code for awarding the trophy goes here...
End Select
End Sub
End Class
The Subject
The notification method is invoked by the object being observed. This is the subject. The subject has two jobs: (1) it holds an array of observers that are waiting to hear from it and (2) it’s responsible for sending notifications. We will implement this as another interface, this time called Subject
that all subjects must implement if they want to be able to make notifications and have observers.
Interface Subject
Sub AddObserver(o As Observer)
Sub RemoveObserver(o As Observer)
Sub Notify(obj As Variant, theEvent As Integer)
End Interface
Let’s change our physics engine to implement the Subject
interface:
Class PhysicsEngine Implements Subject
// The actual physics code is omitted obviously!
// Create a private array to hold our observers.
Private MyObservers() As Observer
Sub AddObserver(o As Observer)
// Part of the Subject interface.
// Add this observer to our private array (only if we don't already know about it).
If MyObsevers.IndexOf(o) = -1 Then MyObservers.AddRow(o)
End Sub
Sub RemoveObserver(o As Observer)
// Part of the Subject interface.
// Remove this observer from our private array (if we know about it).
Var i As Integer = MyObservers.IndexOf(o)
If i <> -1 Then MyObservers.RemoveRowAt(i)
End Sub
Sub Notify(obj As Variant, theEvent As Integer)
// Part of the Subject interface.
// Simply invoke each observer's `NotificationReceived()`
// method and pass in the relevant information.
For Each o As Observer In MyObservers
o.NotificationReceived(obj, theEvent)
Next o
End Sub
End Class
Pretty simple eh?