Observer Pattern Revisited – Delegates and Events
Software Development April 3rd, 2008A 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!!
April 3rd, 2008 at 12:36 pm
It’s also not a bad idea to pull the Position and Name fields out of the concrete classes and put them in the base class.
April 4th, 2008 at 3:14 pm
The usual .NET idiom for doing this kind of thing is to implement the INotifyPropertyChanged interface. It’s a bit different than your version, in that it fires once per property. But it also gives you good leverage with the rest of the framework, since it’s the basis of windows forms data binding and also works with WPF data binding.
Of course it may not be appropriate for any particular application. But in general, it’s nice to use what comes with the framework.
November 7th, 2010 at 10:11 am
Thank you……
Thank you very much. Noble article…
November 13th, 2010 at 12:13 am
… track backe bei http://blog.sekolahasisi.net/?w=ayeshaboucher ……
très bon , votre blog thème est réellement merveilleux , Je suis chasse tout nouveau design pour mon moncler doudoune propre blog , j’aime vôtre, maintenant Je vais à aller recherche le exacte même design !…