A few days ago, I wrote an article on how you could implement the Observer pattern in .NET.  The intent of this article was to show you how this design pattern worked, and I did it without using delegates and events.  This article will run through the same example, but this time the code will use a more traditional .NET event based solution.

Events in .NET allow an object to notify any other interested objects that a particular event has happened.  If you’ve done any forms development (either webforms or winforms) then you are already familiar with all sorts of events, like button clicks, selected index changes, etc.  A delegate is kind of like a type-safe function pointer in .NET.  They are commonly used in event based programming, and when using callback functions, etc.  The event based programming model in .NET inherently uses the observer pattern, with the registration / reregistering / notification of observers being handled by the framework.

I’m going to stick with the same example from the previous example.  We will have several NPC’s that want to react to a player based on the player’s position.  I’ll be using the same Position helper class to represent a 2-D position in the game engine, so if you need a refresher on how that works, please refer to the previous article.  Since we are using the framework to handle the details, we no longer need to use the ISubject and IObserver interfaces that we created.  Instead the Player class has a public event to indicate that they have moved position.   The new Player class now looks like this:

   1: public class Player
   2: {
   3:    private Position _currentPosition;
   4:       
   5:    public Position CurrentPosition
   6:    {
   7:       get { return _currentPosition; }
   8:       set { _currentPosition = value; }
   9:    }
  10:  
  11:    // Set up the event that indicates that the 
  12:    //player changed position.
  13:    public delegate void PositionMovedHandler(Player p);
  14:    public event PositionMovedHandler PositionMoved = delegate { }; 
  15:  
  16:    public Player()
  17:    {
  18:  
  19:    }
  20:  
  21:    // This is the key event in our scenario.  The only thing
  22:    // that the observers care about is the position of the 
  23:    // player, so we notify them when it changes.
  24:    public void SetPosition(Position pos)
  25:    {
  26:       _currentPosition = pos;
  27:       Console.WriteLine("Player is at " + pos.ToString());
  28:  
  29:       // Fire off the position changed event 
  30:       // if there is someone listening.
  31:       PositionMoved(this);
  32:    }
  33: }

Again, since we are letting the framework handle the notifications, we don’t have to handle any of that in our Player class.  There will also be changes to our Archer / Swordsman / FriendlyMage classes from the previous example.  While these classes no longer need to implement our IObserver interface, they will need to know about the player event, and what to do if the event happens.  I did this by adding a public method to each class allowing me to subscribe to the PositionMoved event generated by our Player class.  This is what the Archer class now looks like:

   1: public class Archer 
   2: {
   3:    private string _name;
   4:    private Position _current;
   5:    public string Name
   6:    {
   7:       get { return _name; }
   8:       set { _name = value; }
   9:    }
  10:    public Position Current
  11:    {
  12:       get { return _current; }
  13:       set { _current = value; }
  14:    }
  15:  
  16:    public Archer(string name)
  17:    {
  18:       _name = name;
  19:       _current = new Position(0, 0);
  20:    }
  21:  
  22:    public void SetPosition(Position pos)
  23:    {
  24:       _current = pos;
  25:    }
  26:  
  27:    // This method allows the Archer to react to the 
  28:    // PositionMoved event.
  29:    public void Subscribe(Player p)
  30:    {
  31:       p.PositionMoved 
  32:         += new Player.PositionMovedHandler(Notify);
  33:    }
  34:  
  35:    // This is the same function as in the previous
  36:    // example, it is now acting as a
  37:    // traditional event handler.
  38:    public void Notify(Player p)
  39:    {
  40:       // Archer can attack if use is between 4 and 10
  41:       // units away.
  42:       double distance = p.CurrentPosition.CalcDistance(_current);
  43:       string formatted = String.Format("{0:0.00}", distance);
  44:       if (distance >= 4 && distance <= 10)
  45:          Console.WriteLine(_name
  46:             + " is attacking! [" + formatted + "]");
  47:       else if (distance < 4)
  48:          Console.WriteLine(_name
  49:             + " is too close to attack! [" + formatted + "]");
  50:       else if (distance > 10)
  51:          Console.WriteLine(_name
  52:             + " is too far away to attack! [" + formatted + "]");
  53:    }
  54: }

The same Subscribe method needs to be place in the Swordsman and FriendlyMage classes.  This seems like too much maintenance, and we will address this problem in a little while.  Our classes representing our NCP characters now have to be aware that the PositionMoved event is available from the Player object.  This really isn’t a huge deal though, since they already needed to know information about the Player object in order to calculate the distance between them. (they use the CurrentPosition property of the player in the Notify function)

This actually makes our solution more flexible than when we used our own interfaces.  The same subscribe method could be used to handle any number of events generated by the player.  We could have as many handlers as we needed.  In our previous example it could get ugly trying to support multiple events in a clean fashion.  The reason for this is that by using events, the .NET framework is responsible for keeping track of who is subscribed to each event.  In the previous example. that burden would fall on the Player object.

The last thing we need to cover is how to subscribe to this events from within the game engine.  This turns out to be as easy as calling our Subscribe function, like this:

   1: class Program
   2: {
   3:    static void Main(string[] args)
   4:    {      
   5:       // Set up the player.
   6:       Player p = new Player();
   7:  
   8:       // Set up the NPC's.
   9:       // Calling the Subscribe method registers
  10:       // us to use the event.
  11:       Archer archer1 = new Archer("Robin Hood");
  12:       archer1.SetPosition(new Position(1, 3));
  13:       archer1.Subscribe(p);
  14:  
  15:       Archer archer2 = new Archer("Legolas");
  16:       archer2.SetPosition(new Position(1, 7));
  17:       archer2.Subscribe(p);
  18:  
  19:       Swordsman swordsman1 = new Swordsman("Conan");
  20:       swordsman1.SetPosition(new Position(7, 4));
  21:       swordsman1.Subscribe(p);
  22:  
  23:       Swordsman swordsman2 = new Swordsman("Aragorn");
  24:       swordsman2.SetPosition(new Position(7, 6));
  25:       swordsman2.Subscribe(p);
  26:  
  27:       FriendlyMage mage = new FriendlyMage("Gandalf");
  28:       mage.SetPosition(new Position(11, 9));
  29:       mage.Subscribe(p);
  30:  
  31:       // Move the player.  This fires off the 
  32:       // event we created in our Player class.
  33:       p.SetPosition(new Position(11, 5));
  34:    
  35:       Console.ReadLine();
  36: }

Running this application gives us the same result we would expect:

One other thing I should mention to keep things consistent.  In the last example I was able to unregister an NPC from reacting to events.  That is a simple change here, all we have to do is add an unsubscribe function to our Archer, Swordsman, and FriendlyMage classes, like this:

   1: public void UnSubscribe(Player p)
   2: {
   3:    p.PositionMoved -= new Player.PositionMovedHandler(Notify);
   4: }

This allows us to remove someone any time we choose in the engine, like this:

   1: // This takes the NPC off
   2: // the notification list for 
   3: // Player events.
   4: archer1.UnSubscribe(p);

Remember when I said we’d get back to the problem of maintenance from modifying all of those classes?  The last thing I want to cover is how to clean up our design here a little bit.  Since all of our NPC classes have the same functions, we can create a base class called NPC, and this class could deal with handling the subscribing and unsubscribing to the events….which would clean up our implementation a lot.  This is what our base class would look like:

   1: public abstract class NPC
   2:  
   3:   // These methods are common to all NPC's.
   4:   public void Subscribe(Player p)
   5:   {
   6:      p.PositionMoved += new Player.PositionMovedHandler(Notify);
   7:   }
   8:   public void Unsubscribe(Player p)
   9:   {
  10:      p.PositionMoved -= new Player.PositionMovedHandler(Notify);
  11:   }
  12:  
  13:   // Each NPC will have a unique implementation of this.
  14:   public abstract void Notify(Player p);

After this change, we would make all of our NPC classes inherit from this class, so they would look like this:

   1: public class Archer : NPC
   2: {
   3:    private string _name;
   4:    private Position _current;
   5:    public string Name
   6:    {
   7:       get { return _name; }
   8:       set { _name = value; }
   9:    }
  10:    public Position Current
  11:    {
  12:       get { return _current; }
  13:       set { _current = value; }
  14:    }
  15:  
  16:    public Archer(string name)
  17:    {
  18:       _name = name;
  19:       _current = new Position(0, 0);
  20:    }
  21:  
  22:    public void SetPosition(Position pos)
  23:    {
  24:       _current = pos;
  25:    }
  26:  
  27:    // We are now overriding the definition
  28:    // from our base class.
  29:    public override void Notify(Player p)
  30:    {
  31:       // Archer can attack if use is between 4 and 10
  32:       // units away.
  33:       double distance = p.CurrentPosition.CalcDistance(_current);
  34:       string formatted = String.Format("{0:0.00}", distance);
  35:       if (distance >= 4 && distance <= 10)
  36:          Console.WriteLine(_name
  37:             + " is attacking! [" + formatted + "]");
  38:       else if (distance < 4)
  39:          Console.WriteLine(_name
  40:             + " is too close to attack! [" + formatted + "]");
  41:       else if (distance > 10)
  42:          Console.WriteLine(_name
  43:             + " is too far away to attack! [" + formatted + "]");
  44:    }
  45: }

Making this change cleans up the design, and we would only have to make changes regarding event subscription in the base class rather than in every concrete instance.  As you can see, the .NET framework has built in support for this design pattern right out of the box!!

kick it on DotNetKicks.com