很多人都喜欢玩魔兽争霸, 里面的兵种很多, 比如有 Footman 步兵, Knight 骑士, Grunt 兽人步兵, 他们使用不同的武器, Footman 步兵使用宝剑攻击, knight 也使用 sword 宝剑攻击, grunt 使用 axe 斧头攻击. 我们如何来设计这几个兵种角色呢?
1 初步设计
首先我们想可以创建一个 character 角色类, 他们都能够 walk 走路, 但有不同的攻击行为, 可以将 character 设计成抽象类, 利用继承来得到具体的兵种. 设计出来的类应该如图所示.
这种设计通过继承实现各兵种, footman,knight,grunt 可以实现攻击行为. 我们现在想增加兵种 kedo 科多兽, kedo 使用 mouth 嘴吞噬敌人, 还可以用 drum 战鼓增加我军的攻击力, 是个辅助兵种, character 中没有 kedo 对应的攻击和辅助方法, kedo 不能从 character 继承得到. 如果想让 kedo 继承 character, 必须修改 character 的类结构, 增加用嘴攻击的方法和用鼓辅助的方法.
这里就能看到这个设计存在的问题, 第一个是可扩展性不好, 不能对适应变化的行为. 第二个问题是 footman 和 knight 都用宝剑攻击, swordAttack 方法在子类需要重复写代码, 没有将代码复用.
这里我们引入软件设计的一个原则, 就是区分不变的和变化的, 将变化的封装起来. 具体到 character 类来说, 就是 walk 方法是不变的, 不用动. attack 和 assist 行为是变化的, 我们给封装起来.
2 第一次改进
将 attack 和 assist 行为封装起来, 我们考虑使用接口, 设计两个接口 Attackable 和 Assistable, 让 footman,knight,grunt,kedo 分别实现相应的接口. 设计出来的类如下图所示.
我们看这个设计方法, 第一个可扩展的问题, 如果还有其他的行为, 我们可以再加入接口, 这样可扩展的问题就解决了. 第二个问题, 代码重复的问题, 由于 footman 和 knight 的 swordAttack 实现 Attackable 接口的 attack 方法, 还是存在代码重复的问题.
解决代码重复的问题, 我们考虑将 attack 和 assist 两种行为从 character 中分离出来.
3 第二次改进
3.1 分离出行为
我们设计两个行为的接口, AttackBehavior 和 AssistBehavior, 子类来实现对应的 attack 和 assist 行为. 接口和实现类的设计如图所示.
此处使用了软件设计的一个原则, 针对接口编程, 不要针对实现编程. 针对接口编程, 可以使用面向对象的多态特性, 比如一个兵种有 AttackBehavior 的 attack 行为, 这个 attack 行为在实现的时候可以是 SwordAttack, 也可以 AxeAttack 或者 MouthAttack, 增加灵活性.
3.2 整合兵种的行为
在 Character 类中增加两个实例变量, 分别是 attackBehavior 和 assistBehavior, 声明为接口类型. Character 设计如图所示.
3.3 代码实现
以下是 Character 类的代码实现
- public class Character {
- AttackBehavior attackBehavior;
- AssistBehavior assistBehavior;
- public void walk(){
- System.out.println("I can walk!");
- }
- public void performAttack(){
- attackBehavior.attack();
- }
- public void performAssist(){
- assistBehavior.assist();
- }
- }
以下是 Kedo 科多兽的主要代码
- public class Kedo extends Character {
- public Kedo(){
- attackBehavior = new MouthAttack();
- assistBehavior = new DrumAssist();
- }
- }
我们在这里将兵种的攻击或者辅助行为单独设计为类, 与兵种分离开来, 这种做法让兵种和行为都是独立的个体, 他们的结合方式变成了组合, 而不是一开始的继承. 这里用到了软件设计中一个重要的原则, 多用组合, 少用继承. 使用组合建立的系统具有很大的弹性.
我们的代码 Kedo 科多兽类, 有用嘴吞噬和用战鼓辅助的行为, 代码把他的行为固定了, 如果可以不把他的行为固定, 动态的设定他的行为, 则可以增加代码的灵活性. 我们可以进一步改进代码.
3.4 动态设定行为
在 Character 类中, 增加两个方法:
- public void setAttackBehavior(AttackBehavior atb){
- attackBehavior = atb;
- }
- public void setAssistBehavior(AssistBehavior asb){
- assistBehavior = asb;
- }
这样我们如果实现新的兵种, 比如 Raider 狼骑士 , 他是用大刀 (暂时用宝剑代替) 砍, 我们可以不创建 raider 这个兵种类, 直接实现 raider 具有宝剑攻击的行为. 代码如下.
- Character raider = new Character();
- raider.setAttackBehavior(new SwordAttack());
- raider.performAttack();
4 策略模式
现在我们回过头来再看看整个思路, 兵种具有不同的攻击或辅助行为, 我们把行为分离出来进行定义, 这样就定义了一组行为(SwordAttack,AxeAttack,MouthAttack,DrumAssist), 并把这些行为进行了封装, 都封装到了类中, 这些行为在地位上平等的, 可以互相替代, 行为独立于兵种之外, 这就是策略模式的设计思路.
我们把行为扩展到更广义的算法, 一组行为我们扩展为算法簇. 再把上面的设计思路说一次, 就是策略模式, 定义了算法簇, 分别封装起来, 让它们之间可以互相替换, 此模式让算法的变化独立于使用算法的客户.
来源: https://www.cnblogs.com/coodream2009/p/10540657.html