DOTA中的设计模式(1):策略模式
一、概念
策略模式(Strategy):它定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法的变化不会影响到使用算法的客户。(原文:The Strategy Pattern defines a family of algorithms,encapsulates eachone,and makes them interchangeable. Strategy lets the algorithm varyindependently from clients that use it.)
二、类图

三、存储记忆:在你的大脑里记下类图,同时理解算法簇
(1)Context(应用场景):需要使用Strategy提供的算法。
a. 内部维护一个Strategy的实例。
b. 负责动态设置运行时Strategy具体的实现算法。
c. 负责跟Strategy之间的交互和数据传递。
(2)Strategy(抽象策略类):
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
(3)ConcreteStrategy(具体策略类):
实现了Strategy定义的接口,提供具体的算法实现。
四、DOTA游戏
(1)相信很多人都玩过这款游戏。在这个游戏中,有很多英雄可以选择,而且每种英雄都有不同的技能。每个英雄会有4种或更多的技能。

(2)由于没个英雄的技能都有所不同,那么我们如何实现呢?起初,我的想法很简单,因为每个英雄都有四个技能,那么我定义英雄技能的接口,在由每个英雄分别去实现这个接口。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/// <summary> /// 英雄技能接口 /// </summary> public interface IHeroSkill { IEffect Skill1(); IEffect SKill2(); IEffect Skill3(); IEffect SKill4(); } |
(3)突然发现,有些变态的英雄不止有四个技能。比如说“召唤师”,好家伙,他居然能使用14个技能,咕~~(╯﹏╰)b,这可怎么办呢,难道我要扩充这个接口?
a.假如我在当前接口中添加了....Skill14(),那就麻烦大了,因为这样将导致我们每个英雄都得去实现这些技能,实际上这种方式“太扯蛋了”!
b.突然有人说了一句,我定义N个接口,每个接口里之有一个方法,这样可以吗?这样确实可以解决上面的问题,不过又来了新的问题:接口爆炸,不能再运行时决改变英雄的技能,试想,这样你还能在随意的切换技能吗?
(4)策略模式闪亮登场!由于没个英雄都有多种技能,每个英雄的技能都是不同的,也会造成不同的效果。策略模式中一个关键的概念,定义“算法簇”。在这个游戏中,技能便是一个算法簇,根据策略模式的UML,我们的类图设计如下:
图:有图有真相,手头暂时没有画UML的工具,随后补上O(∩_∩)O~
技能策略接口定义:
|
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
|
/// <summary> /// 技能策略 /// </summary> public abstract class SkillStrategy { #region ==== 公共属性 ==== /// <summary> /// 技能名称 /// </summary> public abstract string Name { get; } /// <summary> /// 技能等级上限 /// </summary> public abstract int MaxLevel { get; } /// <summary> /// 技能等级 /// </summary> public int SkillLevel { get; set; } #endregion #region ==== 公共方法 ==== /// <summary> /// 使用技能产生的效果 /// </summary> /// <param name="heroLevel">技能等级</param> /// <returns>技能效果</returns> public abstract Effect UseKill(); #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
41
42
43
44
45
|
/// <summary> /// 传说哥火枪手的暗杀技能 /// </summary> public class S暗杀:SkillStrategy { #region ==== 构造函数 ==== /// <summary> /// 构造函数 /// </summary> /// <param name="skillLevel">当前技能等级</param> public S暗杀(int skillLevel) : base() { SkillLevel = skillLevel; } #endregion #region ==== 接口实现 ==== public override string Name { get { return "暗杀"; } } public override int MaxLevel { get { return 3; } } public override Effect UseKill() { //TODO } #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
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
|
/// <summary> /// 英雄 /// </summary> public abstract class Hero { #region ==== 公共属性 ==== /// <summary> /// 英雄名称 /// </summary> public string Name { get; set; } /// <summary> /// 血量 /// </summary> public int Blood { get; set; } /// <summary> /// 魔法值 /// </summary> public int Magic { get; set; } /// <summary> /// 经验 /// </summary> public int EX { get; set; } /// <summary> /// 等级 /// </summary> public int Level { get; set; } /// <summary> /// 公共技能 /// </summary> public Dictionary<string, SkillStrategy> HeroSkills { get; set; } #endregion #region ==== 公共方法 ==== /// <summary> /// 升级技能 /// </summary> /// <param name="skillName">技能名称</param> public virtual void UpgradeSkills(string skillName) { //TODO } /// <summary> /// 施放技能 /// </summary> /// <param name="skillName">技能名称</param> /// <param name="hero">目标英雄</param> public virtual void UseSkill(string skillName, Hero hero) { //TODO } #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
|
/// <summary> /// 传说哥 /// </summary> public class Sniper:Hero { #region ==== 构造函数 ==== public Sniper() { var S榴霰弹 = new S榴霰弹(0); var S爆头 = new S爆头(0); var S瞄准 = new S瞄准(0); var S暗杀 = new S暗杀(0); Name = "传说哥"; Blood = 400; Magic = 250; EX = 1; Level = 1; HeroSkills = new Dictionary<string, SkillStrategy>(); HeroSkills.Add(S榴霰弹.Name, S榴霰弹); HeroSkills.Add(S爆头.Name, S爆头); HeroSkills.Add(S瞄准.Name, S瞄准); HeroSkills.Add(S暗杀.Name, S暗杀); } #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
|
/// <summary> /// 施放技能造成的伤害效果 /// </summary> public class Effect { #region ==== 公共属性 ==== /// <summary> /// 掉血量 /// </summary> public int OutBlood { get; set; } /// <summary> /// 眩晕时间 /// </summary> public int GiddinessTime { get; set; } /// <summary> /// 魔法消耗 /// </summary> public int MagicCost { get; set; } #endregion |
测试程序:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Program { static void Main(string[] args) { Sniper sniper = new Sniper(); sniper.UpgradeSkills("榴霰弹"); Lion lion = new Lion(); lion.UpgradeSkills("穿刺"); sniper.UseSkill("榴霰弹", lion); lion.UseSkill("穿刺", sniper); //策略模式已经完成 //策略模式(Strategy):它定义了一系列的算法,将算法封装起来,使它们还可以相互替换。<BR><BR> //看到上面的定义我们现在又了疑问: //比如说召唤师可以切换技能的,使用策略模式就可以很好的解决这个问题,留给读者思考! } } |
五、应用场景和优缺点
(1) 应用场景
a. 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
b. 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现
c. 对客户隐藏具体策略的实现细节,彼此完全独立。
(2) 优缺点
a. 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)[优点]
b. 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展[优点]
c. 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合[优点]
d. 因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量[缺点]

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

浙公网安备 33010602011771号