Design Patterns 101: Observer

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 :face_vomiting:.

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?

I think you have a little error in there:

  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

should have the object reference after IndexOf

    Var i As Integer = MyObservers.IndexOf( o )

I also prefer a little renaming as I find this easier to understand:

Interface iReceiver 
  Sub NotificationReceived(sender As Variant, theMessage As Integer) 
End Interface


Class TrophySystem Implements iReceiver
  Sub NotificationReceived(Sender As Variant, theMessage As Integer)
    Select Case theMessage
      Case GOING_STRATOSPHERIC
        // Code for awarding the trophy goes here...
    End Select
  End Sub
End Class


Interface iSender
  Sub AddReceiver(Receiver As iReceiver)
  Sub RemoveReceiver(Receiver As iReceiver)
  Sub Notify(Sender As Variant, theMessage As Integer)
End Interface


Class PhysicsEngine Implements iSender
  // The actual physics code is omitted obviously!

  // Create a private array to hold our receivers.
  Private MyReceivers() As iReceiver

  Sub AddReceiver(Receiver As iReceiver)
    // Part of the Sender interface.
    // Add this receiver to our private array (only if we don't already know about it).
    If MyReceivers.IndexOf( Receiver ) = -1 Then MyReceivers.AddRow( Receiver )
  End Sub

  Sub RemoveReceiver(Receiver As iReceiver)
    // Part of the Sender interface.
    // Remove this sender from our private array (if we know about it).
    Var rowIndex As Integer = MyReceivers.IndexOf( Receiver )
    If rowIndex <> -1 Then MyReceivers.RemoveRowAt( rowIndex )
  End Sub

  Sub Notify(Sender As Variant, theMessage As Integer)
    // Part of the Sender interface.
    // Simply invoke each Receiver's `NotificationReceived()` 
    // method and pass in the relevant information.
    For Each Receiver As iReceiver In MyReceivers
      Receiver.NotificationReceived( Sender, theMessage )
    Next Receiver
  End Sub
End Class

Correct. Fixed - thanks.