设计模式-策略模式

1. 简介

策略模式属于对象的行为模式。
其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
策略模式使得算法可以在不影响到客户端的情况下发生变化。

  • 2. 设计原则

    • 找出项目中不稳定的代码(可能需要改动的地方),把它们独立出来,不要把它们和其它稳定的代码混在一起
    • 针对接口编程而不是针对实现编程
    • 多用组合少用继承

3. 模式结构

策略模式是对算法的包装,是把调用算法的责任(行为)和算法本身(行为实现)分割开来,委派给不同的对象管理。

策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。下面就以一个示意性的实现讲解策略模式实例的结构。

img

这个模式涉及到三个角色:

  • 环境(Context)角色:
    持有一个Strategy的引用,即具有复杂多变行为的对象。
  • 抽象策略(Strategy)角色:
    这是一个抽象角色,通常由一个接口或抽象类实现。
    此角色给出所有的具体策略类所需的接口。
  • 具体策略(ConcreteStrategy)角色:
    包装了相关的算法或行为。

4. 模式案例

4.1 案例描述

你将设计一个ARPG文字冒险小游戏,你需要设计以下内容:

  • 四个角色:
    女王Queen;国王King;骑士Knight;巨魔Troll;
  • 四种武器:
    匕首Knife;弓箭BowAndArrow;斧头Axe;直剑Sword;

每个角色一次只能使用一种武器,但是在游戏的过程中可以切换武器。

让他们相互攻击,直到有人胜出。

4.2 案例分析

423180E36C6DA4925016AA187CA09C05

由上图可以看出类的结构为
女王Queen;国王King;骑士Knight;巨魔Troll;继承于Character。
匕首Knife;弓箭BowAndArrow;斧头Axe;直剑Sword;实现了WeaponBehavior接口。

由此可以分析出在策略模式中他们的角色:

  • 环境(Context)角色:Character
  • 抽象策略(Strategy)角色:WeaponBehavior
  • 具体策略(ConcreteStrategy)角色:
    KnifeBehavior,BowAndArrowBehavior,AxeBehavior,SwordBehavior

4.3 代码编写

4.3.1 环境角色编写

编写所有角色的基类Character:

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
public class Character
{
private string name;
private int hp;
private int energy;
WeaponBehavior weapon;
public string Name { get => name; set => name = value; }
public int Hp { get => hp; set => hp = value; }
public int Energy { get => energy; set => energy = value; }

public Character(string name,int hp,int energy)
{
this.name = name;
this.hp = hp;
this.energy = energy;
}
protected void setWeapon(WeaponBehavior weapon)//设置武器
{
this.weapon = weapon;
}
public bool fight(Character A)//攻击!
{
weapon.useWeapon(this,A);
if (A.Hp < 0)
{
Console.WriteLine(A.Name + "被" + this.name + "杀死了");
return true;
}
return false;
}
public void showState()//显示状态
{
Console.WriteLine(Name+":血量 "+ hp+"精力 "+energy);
}
}

编写四种角色类继承Character:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Queen:Character
{
public Queen(string name) : base(name, 10, 15) => base.setWeapon(new KnifeBehavior());
}
public class King : Character
{
public King(string name) : base(name, 5, 20) => base.setWeapon(new SwordBehavior());
}
public class Knight : Character
{
public Knight(string name) : base(name, 20, 10) => base.setWeapon(new SwordBehavior());
}
public class Troll : Character
{
public Troll(string name) : base(name, 15, 15) => base.setWeapon(new AexBehavior());
}

4.3.2 抽象策略角色编写

编写武器行为接口:

1
2
3
4
public interface WeaponBehavior
{
public void useWeapon(Character A,Character B);
}

4.3.3 具体策略角色编写

编写具体的武器行为,实现接口:

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
public class KnifeBehavior:WeaponBehavior
{
public void useWeapon(Character A, Character B)
{
if (A.Energy < 1) return;
Console.WriteLine(A.Name+"对"+B.Name+"使用了 匕首 刺杀");
B.Hp--;
A.Energy --;
}
}
public class BowandArrowBehavior : WeaponBehavior
{
public void useWeapon(Character A, Character B)
{
if (A.Energy < 1) return;
Console.WriteLine(A.Name + "对" + B.Name + "射了一箭");
B.Hp--;
A.Energy--;
}
}
public class AexBehavior : WeaponBehavior
{
public void useWeapon(Character A, Character B)
{
if (A.Energy < 3) return;
Console.WriteLine(A.Name + "对" + B.Name + "使用了 斧头 劈砍");
B.Hp-=3;
B.Energy -= 3;
A.Energy-=3;
}
}
public class SwordBehavior : WeaponBehavior
{
public void useWeapon(Character A, Character B)
{
if (A.Energy < 3) return;
Console.WriteLine(A.Name + "对" + B.Name + "使用了 直剑 挥击");
B.Hp -= 5;
B.Energy -= 3;
A.Energy -= 3;
}
}

4.3.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
static void Main(string[] args)
{
Random random = new Random();
List<Character> c = new List<Character>(4);//将各种角色类都装箱成Character
c.Add(new King("大清皇帝"));
c.Add(new Troll("林登万"));
c.Add(new Knight("堂吉诃德"));
c.Add(new Queen("玛丽"));
int i = 4;
while (c.Count != 1)
{
int a, b;
a = random.Next(0, i);
//随机选取两人PK,直到剩下最后一位角色活着
while ((b = random.Next(0, i)) == a)
{
b = random.Next(0, i);
}
c[a].showState();
c[b].showState();
if (c[a].fight(c[b]))
{
c.RemoveAt(b);
i--;
}
Console.WriteLine("");
//每回合恢复体力
for(int j=0;j<c.Count;j++)
{
c[j].Energy++;
}
}
Console.WriteLine("胜者是:" + c[0].Name + "!");
}

这次中世纪文字吃鸡的结果是:

策略模式测试

5.对策略模式的深入认识

5.1 策略模式对多态的使用

 通过让环境类持有一个抽象策略类(超类)的引用,在生成环境类实例对象时,让该引用指向具体的策略子类。再对应的方法调用中,就会通过Java的多态,调用对应策略子类的方法。从而可以相互替换,不需要修改环境类内部的实现。同时,在有新的需求的情况下,也只需要修改策略类即可,降低与环境类之间的耦合度。

5.2 策略模式的重心

策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

5.3算法的平等性

策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。

所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。

5.4 运行时策略的唯一性

运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。

5.5 公有的行为

经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。

这其实也是典型的将代码向继承等级结构的上方集中的标准做法。

img

6. 策略模式的优缺点

6.1 相较于纯继承的优点

  • 代码复用度高,子类中几乎没有重复

  • 很容易知道该类的全部策略(算法)

  • 在运行时可以更容易的更改策略

  • 改动时不会牵一发而动全身,代码耦合度低

6.2 策略模式的优点

  • 策略模式提供了管理相关的算法族的办法。
    策略类的等级结构定义了一个算法或行为族。
    恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
  • 使用策略模式可以避免使用多重条件(if-else)语句。
    多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

6.3策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
    这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。

  • 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

总结自: