Wednesday, November 23, 2011

The Observer Pattern

Often times, objects need to know what other objects are up to. They may need to be updated of changes and passed new data.
A common example is the view or UI of a program needs to be aware of the state of the objects. In a game, the HUD (Heads Up Display) may need to know information like the players hit points, ammo, or score.
This can be easily achieved through the Observer Pattern. The classes that are kept up to date are called observers and the object being observed is often called the subject. The subject usually has a collection of observers as well as methods for adding or removing observers, also called registering and unregistering respectively. There must also be a way of notifying the registered observers of new data when necessary.
Like many design patterns there are multiple ways doing things and the observer pattern is no exception. I'll go into two main ways that observers are notified of change.

Here's a quick look at a UML diagram for the Observer pattern.


In our simplified example we'll stick with the gaming theme where our subject will be a player, and our observer will be the HUD that will be notified of any change to the player's hit points so that it can be displayed. There can be any number of observers, but we'll stick with one for now. (In fact Observers can register with multiple Subjects, Subjects can, themselves be Observers and vice-versa but we'll keep things simple for now.)

First we'll need our observer interface. It will simply contain a method stub for receiving new data.
public interface IObserver
{
    void Update(double hitPoints);
}

Our Subject contains stubs for adding and removing observers and notifying them of changes.
public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void UnregisterObserver(IObserver observer);
    void NotifyObservers();
}

Let's code our Player class. This will inherit from ISubject and will need to contain a collection of observers and data to be observed.
public class Player : ISubject
{
    //Here's some data that needs to be observed.
    double _hitPoints = 20;

    //We need a collection of observers to notify.
    List<IObserver> _observers = new List<IObserver>();

    //Here's the method that actually changes the hitpoints.
    public void TakeDamage(double damageTaken)
    {
        _hitPoints -= damageTaken;
        //Once this field has been altered, 
        //we automatically notify its observers.
        StateChanged();
    }
   
    public void StateChanged()
    {
        //The observers are notified whenever the class's state, 
        //in this case the Player's hit points changes.
        NotifyObservers();
    }

    public void RegisterObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void UnregisterObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyObservers()
    {
        //Here we simply iterate through each registered 
        //observer and pass each one
        //the current hitpoints.
        foreach (IObserver observer in _observers)
        {
            observer.Update(_hitPoints);
        }
    }
}
Notice that whenever the _hitPoints field is changed we call StateChanged() which notifies each observer of the new _hitPoints value.

Now for our concrete observer, the HUD, which will display the Player's hit points if it's registered with it.
class HUD : IObserver
{
    double _hitPointsToDisplay;
    
    //Here is where the observer receives its new data.
    public void Update(double hitPoints)
    {
        _hitPointsToDisplay = hitPoints;
    }

    public void DisplayHitPoints()
    {
        Console.WriteLine("Hit Points: ");
        Console.WriteLine(_hitPointsToDisplay.ToString());
    }   
}
We can now set up a simple test:
class Program
{
    static void Main(string[] args)
    {
        Player _player = new Player();
        HUD _hud = new HUD();

        //Register the HUD observer.
        _player.RegisterObserver(_hud);

        //Once all the observers are registered we'll 
        //initialize them with the proper data.
        _player.StateChanged();

        _hud.DisplayHitPoints();

        //The player takes some damage.  Remember that this 
        //method automatically notifies all
        //observers so there's no reason to call 
        //the NotifyObservers() method again.
        _player.TakeDamage(15.25);

        //The HUD displays the new data.
        _hud.DisplayHitPoints();

        Console.ReadLine();
    }
}

And it works as promised. Every time the Player takes damage (the TakeDamage() method is called), the HUD is notified of the change and we can display it on the screen.

However, there will almost definitely be other data associated with the concrete subject that the observers will need to be aware of.
The Player may have a Score for example. So we'll need to change the classes a bit.
public interface IObserver
{
    void Update(double hitPoints, int score);
}

The Player class now has a score field and a method for incrementing it. Here's the entire Player class with the new changes.
public class Player : ISubject
{
    //Here's some data that needs to be observed.
    double _hitPoints = 20;
    int _score = 0;

    //We need a collection of observers to notify.
    List<IObserver> _observers = new List<IObserver>();

    //Here's the method that actually changes the hitpoints.
    public void TakeDamage(double damageTaken)
   {
        _hitPoints -= damageTaken;
        //Once this field has been altered, we automatically notify its observers.
        StateChanged();
   }

    //This method adds to the player's score.
    public void AddScore(int score)
    {
        _score += score;
        //And again, we notify observers.
        StateChanged();
    }

    public void RegisterObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void UnregisterObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void StateChanged()    
    {        
        //The observers are notified whenever the class's state,
        //in this case the Player's hit points changes.
        NotifyObservers();
    }

    public void NotifyObservers()
    {
        //Here we simply iterate through each registered 
        //observer and pass each one the current hitpoints.
        foreach (IObserver observer in _observers)
        {
            observer.Update(_hitPoints, _score);
        }
    }
}

Finally the HUD needs to be modified to reflect these changes.
class HUD : IObserver
{
    double _hitPointsToDisplay;
    //Here's the new data that the HUD is interested in keeping up to date with.
    int _scoreToDisplay;

    //Here is where the observer receives its new data.
    public void Update(double hitPoints, int score)
    {
        _hitPointsToDisplay = hitPoints;
        _scoreToDisplay = score;
    }

    public void DisplayHitPoints()
    {
        Console.WriteLine(_hitPointsToDisplay.ToString());
    }

    public void DisplayScore()
    {
        Console.WriteLine(_scoreToDisplay.ToString());
    }
}

Here's the modified program class.
class Program
{
    static void Main(string[] args)
    {
        Player _player = new Player();
        HUD _hud = new HUD();

        //Register the HUD observer.
        _player.RegisterObserver(_hud);

        //Once all the observers are registered we'll 
        //initialize them with the proper data.
        //We can also do this in the concrete observers' constructors.
        _player.NotifyObservers();

        //Display initial values.
        _hud.DisplayHitPoints();
        _hud.DisplayScore();

        //The player takes some damage.  Remember that this 
        //method automatically notifies all
        //observers so there's no reason to call 
        //the NotifyObservers() method again.
        _player.TakeDamage(15.25);

        //The player receives a score for some reason...
        _player.AddScore(5);

        //The HUD displays the new data.
        _hud.DisplayHitPoints();
        _hud.DisplayScore();

        Console.ReadLine();
    }
}

We can already see some problems cropping up. Each piece data we start adding in the player class, we'll need to modify the all the classes that inherit from IObserver.
One way to deal with this is to modify the IObserver interface so that it's Update method stub takes an object as a parameter so one could pass any type of data. It would then be up to the concrete observer class to interpret the argument, which can be tricky. I've set this example up so that each piece of data is of a different type so we won't run into a lot of problems.

Here's our modified IObserver interface.
public interface IObserver
{
    void Update(object arg);
}

The ISubject interface also needs to change.
public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void UnregisterObserver(IObserver observer);
    void NotifyObservers(object arg);
}

The new HUD will receive data as an object type, interpret it and update the appropriate fields.
class HUD : IObserver
{
    double _hitPointsToDisplay;
    int _scoreToDisplay;

    //Here is where the observer receives its new data.
    public void Update(object arg)
    {
        //It's up to the observer to handle the argument passed
        //to it, which can be quite a hassle!
        if (arg is double)
        {
            _hitPointsToDisplay = (double)arg;
        }
        else if (arg is int)
        {
            _scoreToDisplay = (int)arg;
        }
    }

    public void DisplayHitPoints()
    {
        Console.WriteLine("Hit Points: ");
        Console.WriteLine(_hitPointsToDisplay.ToString());
    }

    public void DisplayScore()
    {
        Console.WriteLine("Score: ");
        Console.WriteLine(_scoreToDisplay.ToString());
    }
}

Now we just need to modify the concrete subject, our Player class, to correctly notify its observers.
public class Player : ISubject
{
    //Here's some data that needs to be observed.
    double _hitPoints = 20d;
    int _score = 0;

    //We need a collection of observers to notify.
    List<IObserver> _observers = new List<IObserver>();

    //Here's the method that actually changes the hitpoints.
    public void TakeDamage(double damageTaken)
    {
        _hitPoints -= damageTaken;
        //Once this field has been altered, we automatically notify its observers.
        NotifyObservers(_hitPoints);
    }

    //This method adds to the player's score.
    public void AddScore(int score)
    {
        _score += score;
        //And again, we notify observers.
        NotifyObservers(_score);
    }

    public void RegisterObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void UnregisterObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyObservers(object arg)
    {
        //Here we simply iterate through each registered observer and pass each one
        //the current hitpoints.
        foreach (IObserver observer in _observers)
        {
            observer.Update(arg);
        }
    }
}

I've done away with the StateChanged() method and now simply call NotifyObservers(arg) with the appropriate piece of data.

One thing to notice is that the Subject decides what data gets passed to the observers via the Update method. This is called the push method of notification since the Subject pushes the data onto its observers. This may not always be what we want. There may be an observer that's interested in some of the data its subject has but not all of it. Another method, called the pull method, allows the observers to pick and choose which data to take from its subject. Implementing it is not difficult and requires only a few changes to our existing code. I have decided to make the Subject and abstract method so that it can handle the observer collection internally.
The basic idea of the pull method is that instead of passing specific pieces of data to our observers, we pass the Subject object as the Update's argument. As long as the Subject implements some way of getting its data, the observers can decide what to take from its subject and what to ignore.

First let's modify our new IObserver interface.
public interface IObserver
{
    void Update(Subject subject, object arg);
}
Now it takes a Subject object as a parameter as well. We can keep the object arg if we ever want to pass specific data.

Our Subject class is abstract and handles the collection and notification:
public abstract class Subject
{
    List<IObserver> _observers = new List<IObserver>();

    public void RegisterObserver(IObserver observer)
    {
        _observers.Add(observer);
    }

    void UnregisterObserver(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyObservers(object arg)
    {
        foreach (IObserver observer in _observers)
        {
            observer.Update(this, arg);
        }
    }

    public void NotifyObservers()
    {
        NotifyObservers(null);
    }
}
We now have two different NotifyObserver methods. One can pass a specific piece of data for an observer to deal with and the other passes null for that argument while they both pass along the Subject.

Our Player class no longer needs to handle its observer collection.
public class Player : Subject
{
    //Here's some data that needs to be observed.
    double _hitPoints = 20d;
    int _score = 0;

    //Here's the method that actually changes the hitpoints.
    public void TakeDamage(double damageTaken)
    {
        _hitPoints -= damageTaken;
        //Once this field has been altered, we automatically notify its observers.
        NotifyObservers();
    }

    //This method adds to the player's score.
    public void AddScore(int score)
    {
        _score += score;
        //And again, we notify observers.
        NotifyObservers();
    }

    public double GetHitPoints()
    {
        return _hitPoints;
    }

    public int GetScore()
    {
        return _score;
    }
}
I'm using the pull method here to show how it works but we don't have to. Just remember that the object parameter might be null and our Observers have to handle cases like this. But for this quick example I'll go ahead and assume we're simply interested in the Subject argument.
class HUD : IObserver
{
    double _hitPointsToDisplay;
    int _scoreToDisplay;

    //Here is where the observer receives its new data.
    public void Update(Subject subject, object arg)
    {
        if (subject is Player)
        {
            Player player = subject as Player;
            _hitPointsToDisplay = player.GetHitPoints();
            _scoreToDisplay = player.GetScore();
        }
    }

    public void DisplayHitPoints()
    {
        Console.WriteLine("Hit Points: ");
        Console.WriteLine(_hitPointsToDisplay.ToString());
    }

    public void DisplayScore()
    {
        Console.WriteLine("Score: ");
        Console.WriteLine(_scoreToDisplay.ToString());
    }
}
In the Update method we grab only the data we want. The subject could have many more fields but our HUD is interested only in the hit points and score.

There you have it. Three different methods of implementing the observer pattern. Each with their own advantages and drawbacks. Deciding which method to use depends on the problem at hand and personal preference. And these are by no means the only ways of doing it, the observer pattern, like most patterns can be tweaked and modified to fit your own needs.

I hope you enjoyed the article and thanks for reading!

2 comments :

  1. Nice article but there is some problems with coupling between Observer and Subject in your push system.

    Maybe it's better to send a string (or code) to know what kind of event we need to deal with.

    ReplyDelete
  2. Thank you! And yes, you are absolutely correct, that 'object arg' parameter has problems. I've thought about this before. Strings always seem messy... reflection is slow and also messy... I've thought about wrapping the data in a descriptive class? Similar to EventArgs or something. Haven't really tried it though.

    ReplyDelete