DOTA中的设计模式(2):观察者模式
一、概念
观察者模式(Strategy):它定义了对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。(原文:The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.)
二、类图

三、存储记忆:在你的大脑里记下类图,同时理解以下对象的职责与行为
(1)Subject(被观察的对象接口)
a. 规定ConcreteSubject的统一接口;
b. 每个Subject可以有多个Observer;
(2)ConcreteSubject(具体被观察对象)
a. 维护对所有具体观察者的引用的列表;
b. 状态发生变化时会发送通知给所有注册的观察者。
(3)Observer(观察者接口)
a. 规定ConcreteObserver的统一接口;
b. 定义了一个update()方法,在被观察对象状态改变时会被调用。
(4)ConcreteObserver(具体观察者)
a. 维护一个对ConcreteSubject的引用;
b. 特定状态与ConcreteSubject同步;
c. 实现Observer接口,通过update()方法接收ConcreteSubject的通知。
四、DOTA游戏
(1)在这款游戏中,观察者模式到处都可以找到例子。比如说,我们使用了火枪哥,由于对线作战挣钱比较困难,火枪哥毅然决然的选择了“打野怪”赚点外快!

(2)火枪哥发起了对“野怪们”的攻击,野怪们发现了有个不要命的家伙在打他们,于是进行了反击!火枪哥被围殴了,发现自己的血量已经被野怪干到了300,不行了,赶紧跑吧,于是野怪们也停止了想这个家伙的攻击,在自己的领地内继续快乐的生活O(∩_∩)O~,这是一个典型的观察者模式,我们一起看看如何实现:
类图,暂略。随后补上...
(3)在这个模型中,野怪们是作为观察者:谁打我们,我们打谁,我们观察者的接口设计如下:
|
1
2
3
4
5
6
7
|
/// <summary> /// 观察者:野怪 /// </summary> public interface IObserverMonster { void Attack(Hero hero); } |
(4)我们设计了具体的观察者对象:野怪(狗头人,食人魔)。由于我们的野怪具有一些公共的属性,因此我们设计了一个野怪的抽象类:
野怪抽象类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/// <summary> /// 野怪抽象类 /// </summary> public abstract class Monster { #region /// <summary> /// 野怪类别的名字 /// </summary> public abstract string Name { get; } /// <summary> /// 野怪等级 /// </summary> public virtual int Level { get; set; } /// <summary> /// 攻击力 /// </summary> public virtual int AttackForce { get; set; } #endregion } |
狗头人:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/// <summary> /// 狗头人 /// </summary> public class M狗头人 : Monster,IObserverMonster { #region ==== 构造函数 ==== /// <summary> /// 初始化狗头人 /// </summary> /// <param name="level">等级</param> /// <param name="attackForce">攻击力</param> public M狗头人(int level, int attackForce) : base() { this.Level = level; this.AttackForce = attackForce; } #endregion #region ==== 接口实现 ==== public override string Name { get { return "狗头人"; } } public void Attack(Hero hero) { hero.Blood -= AttackForce; Console.WriteLine("一只{0}开始攻击{1},造成{2}点伤害,{1}当前血量{3}", this.Name, hero.Name, this.AttackForce, hero.Blood); } #endregion } |
食人魔:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/// <summary> /// 食人魔 /// </summary> public class M食人魔:Monster,IObserverMonster { #region ==== 构造函数 ==== /// <summary> /// 初始化食人魔 /// </summary> /// <param name="level">等级</param> /// <param name="attackForce">攻击力</param> public M食人魔(int level, int attackForce) : base() { this.Level = level; this.AttackForce = attackForce; } #endregion #region ==== 接口实现 ==== public override string Name { get { return "食人魔"; } } public void Attack(Hero hero) { hero.Blood -= AttackForce; Console.WriteLine("一只{0}开始攻击{1},造成{2}点伤害,{1}当前血量{3}",this.Name,hero.Name,this.AttackForce,hero.Blood); } #endregion } |
(5)有了观察者,就 需要有被观察者对象。在这个模型中,英雄去打野,野怪发现有英雄打他们,就发起反击。所以,这里英雄是作为被观察者的,我们先设计了一个被观察者对象的接口:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/// <summary> /// 被观察者:英雄 /// </summary> public interface ISubjectHero { #region ==== 接口实现 ==== /// <summary> /// 注册观察者 /// </summary> /// <param name="observer">观察者</param> void RegisterObserver(IObserverMonster observer); /// <summary> /// 移除观察者 /// </summary> /// <param name="observer">观察者</param> void RemoveObserver(IObserverMonster observer); /// <summary> /// 通知观察者 /// </summary> void NotifyObserver(); #endregion |
(6)我们设计了一个英雄抽象类,并实现该接口:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
/// <summary> /// 英雄抽象类 /// </summary> public abstract class Hero:ISubjectHero { #region ==== 私有字段 ==== /// <summary> /// 英雄攻击的野怪们 /// </summary> List<IObserverMonster> observers = new List<IObserverMonster>(); #endregion #region ==== 公共属性 ==== /// <summary> /// 英雄名称 /// </summary> public abstract string Name { get; } /// <summary> /// 血量 /// </summary> public virtual int Blood { get; set; } #endregion #region ==== 公共方法 ==== /// <summary> /// 向野怪们发起攻击 /// </summary> /// <param name="monsters">野怪们</param> public virtual void Attack(List<Monster> monsters) { Console.WriteLine("英雄{0}正在打野...", this.Name); if (monsters != null && monsters.Count > 0) { foreach (Monster monster in monsters) { RegisterObserver(monster as IObserverMonster); } NotifyObserver(); } } public virtual void Escape(List<Monster> monsters) { if (monsters != null && monsters.Count > 0) { foreach (Monster monster in monsters) { RemoveObserver(monster as IObserverMonster); Console.WriteLine("一只野怪{0}停止了攻击", monster.Name); } NotifyObserver(); } } public void RegisterObserver(IObserverMonster observer) { if (!observers.Contains(observer)) { observers.Add(observer); } } public void RemoveObserver(IObserverMonster observer) { if (observers.Contains(observer)) { observers.Remove(observer); } } public void NotifyObserver() { if (observers.Count <= 0) { return; } foreach (IObserverMonster observer in observers) { observer.Attack(this); } } #endregion } |
(7)我们创建了一个英雄,火枪哥:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/// <summary> /// 英雄:火枪哥 /// </summary> public class Sniper:Hero { #region ==== 公共属性 ==== public override string Name { get { return "狙击手"; } } #endregion #region ==== 构造函数 ==== public Sniper() { this.Blood = 500; Console.WriteLine("您使用了:{0},当前血量:{1}", Name, Blood); } #endregion } |
(8)nice,我们已经成功构建了观察者模型,好,我们开始玩游戏吧,来测试下:
测试程序:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Program { static void Main(string[] args) { M狗头人 M狗头人1 = new M狗头人(1, 10); M狗头人 M狗头人2 = new M狗头人(2, 15); M食人魔 M食人魔1 = new M食人魔(3, 20); List<Monster> monsterGroup = new List<Monster>() { M狗头人1, M狗头人2, M食人魔1 }; Sniper sniper = new Sniper(); //血量不小于300时,火枪哥在一直打野 while (sniper.Blood > 300) { sniper.Attack(monsterGroup); } //哇,火枪哥顶不住了,赶紧跑吧,野怪哥们,别打我了 sniper.Escape(monsterGroup); } } |
测试结果:

(9)以上我们通过观察者模式实现了这个游戏,在.NET里,我们可以通过事件委托来实现相应功能,留给读者思考。
五、应用场景和优缺点
(1) 应用场景
a. 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
b. 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
(2) 优缺点
a. Subject和Observer之间是松偶合的,分别可以各自独立改变[优点]
b. Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知[优点]
c. 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合[优点]
d. 松偶合导致代码关系不明显,有时可能难以理解[缺点]
e. 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题[缺点]

出处:http://traxex.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号