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.
The implementation is really easy, it needs two participants: the
Observer and the
Any object that wants to know when something interesting happens to another object must implement our
Interface Observer Sub NotificationReceived(obj As Variant, theEvent As Integer) End Interface
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 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
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?