策略模式
定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
举一个简单的例子来描述策略模式。
设计一款冷兵器时代士兵打仗的游戏,游戏内部设计要使用OO技术。
首先,设计一个士兵对象(Soilder)作为父类,然后在设计许多不同种类的士兵对象来继承士兵这个父类,比如:长枪兵(Spearman)、骑兵(Cavalryman)、弓箭手(Bowman)等等,设计好后进行讨论,觉得不错没有问题,可以开始开发,测试,游戏公测……一切都不错,具体如下面的类图所示:
在游戏运营了一段时候之后,发现需要一些新元素来吸引玩家,要求不同种类的士兵的攻击动作不同,并且要求士兵需要有游泳、攀爬等技能。使用OO技术非常好解决啊,打开类的设计图开始干活吧!可以把hit方法设计成抽象方法,让每种士兵都实现自己的hit,然后在Soilder类中加入游泳,攀爬的方法,修改的类图如下:
使用这种设计方式后,在游戏运行的过程中我们会看到,骑兵骑着马在爬树。显然的,在设计的时候忽略了一点,并非所有的士兵都可以游泳和攀爬。有许多不可做这些行为的士兵,对代码的局部修改,影响的层面可不仅是局部。
使用继承如何?
把swim和clmb放到子类中,覆盖掉父类的对应方法,像hit的做法一样。可如果以后要加入其他类型的士兵又会如何?比如重甲步兵没办法游泳也没办法攀爬,因为重甲太重了,而骑兵则无法骑着马攀爬,但可以骑着马过河等等。可见利用继承来提供士兵的行为会造成:
1.代码在多个子类中重复;
2.运行时的行为不容易改变;
3.很难知道所有士兵的全部行为;
4.改变会牵一发而动全身,造成其他某些类型的士兵不想要的行为;
利用接口如何?
可以加入ISwin和IClimb接口,把swim函数和climb函数从父类中取出来,并被子类实现,如下图所示:
但士兵的规格会常常改变,每当有新的士兵类型出现,就要被迫检查兵可能需要实现ISwim和IClimb接口。并且这么以来重复的代码会变多,因为实现的可以游泳和爬树的代码是一样的。比如有一天swim的方式发生了变化,现有的50个士兵子类都需要修改swim函数,这是不可以接受的。
如何解决?
可以看出,并非所有的子类都会游泳和爬树,所以继承并不是适当的解决方式。虽然使用接口可以解决一部分问题,但是却造成了代码无法复用,幸运的是,有一个原则恰好用于此处。
设计原则1
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
也就是说,每次需求一来,都会使某些方面的代码发生变化,那么基本可以确定这部分代码需要被抽出来和其他稳定的代码有所区分。
现在,为了要分开变化和不会变化的部分,我们准备建立两组类,一个是swim相关的,一个是clmb相关的,并且要做到一切能有弹性,正因为一开始设计的行为没有弹性才导致种种问题。比方说,我要产生一个新的类型的轻甲士兵,并指定行为攀爬给他,也就是说在士兵类中包涵设置行为的方法,这样就可以在运行时动态改变行为,有了这些目标要实现,就引出了第二个设计原则。
设计原则2
针对接口或抽象类编程,而不是针对实现编程。
我们利用接口代表每个行为(Behavior),比方说SwimBehavior和ClimbBehavior,而行为的每个实现都将实现其中的一个接口。Soilder类不会负责实现它们,反而是由一组其他类专门去实现,并整合到Soilder类中,具体的做法是在Soilder类中加入两个实例变量,分别为SwimBehavior和ClimbBehavior,声明为接口类型,每个士兵子类都会动态的设置这些变量在运行时引用正确的行为类型。看一下类图与实现的代码(Java代码)。

package cn.net.bysoft.Strategy; // 士兵 public abstract class Soldier { // 士兵的名字 private String name; private SwimBehavior swimBehavior; private ClimbBehavior climbBehavior; public Soldier() { super(); } public Soldier(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public SwimBehavior getSwimBehavior() { return swimBehavior; } public void setSwimBehavior(SwimBehavior swimBehavior) { this.swimBehavior = swimBehavior; } public ClimbBehavior getClimbBehavior() { return climbBehavior; } public void setClimbBehavior(ClimbBehavior climbBehavior) { this.climbBehavior = climbBehavior; } // 显示士兵的信息 public void display() { System.out.println("这个士兵的名字:" + name); } // 士兵攻击敌人。 public abstract void hit(); // 委托给行为去处理。 public void swim() { swimBehavior.swim(); } public void climb() { climbBehavior.climb(); } }

package cn.net.bysoft.Strategy; // 长枪兵 public class Spearman extends Soldier { public Spearman() {} public Spearman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("士兵使用长枪去攻击敌人"); } } package cn.net.bysoft.Strategy; // 骑兵 public class Cavalryman extends Soldier { public Cavalryman() {} public Cavalryman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("骑士在马上用刀攻击敌人"); } } package cn.net.bysoft.Strategy; // 弓箭手 public class Bowman extends Soldier { public Bowman() {} public Bowman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("弓箭手拉弓瞄准射击敌人"); } }

package cn.net.bysoft.Strategy; // 游泳行为的接口。 public interface SwimBehavior { // 只需在此定义一个游泳的行为方法即可。 public void swim(); } package cn.net.bysoft.Strategy; // 不能游泳 public class SwimNoWay implements SwimBehavior { public void swim() { // TODO Auto-generated method stub System.out.println("这个士兵不能游泳"); } } package cn.net.bysoft.Strategy; // 士兵自己游泳,不通过其他途径。 public class SwimWithBody implements SwimBehavior{ public void swim() { // TODO Auto-generated method stub System.out.println("士兵自己游泳过河"); } } package cn.net.bysoft.Strategy; // 士兵骑马游泳 public class SwimWithHouse implements SwimBehavior { public void swim() { // TODO Auto-generated method stub System.out.println("士兵骑马过河"); } }

package cn.net.bysoft.Strategy; // 攀爬行为的接口 public interface ClimbBehavior { public void climb(); } package cn.net.bysoft.Strategy; public class ClimbNoWay implements ClimbBehavior { public void climb() { // TODO Auto-generated method stub System.out.println("这个士兵不能攀爬"); } } package cn.net.bysoft.Strategy; // 士兵爬树。 public class ClimbTree implements ClimbBehavior { public void climb() { // TODO Auto-generated method stub System.out.println("士兵开始爬树"); } }

package cn.net.bysoft.Strategy; public class Game { public static void main(String[] args) { // TODO Auto-generated method stub // 一个玩家建立一个长矛兵进入游戏 Soldier spearman = new Spearman("长矛高手"); spearman.hit(); // 设置这个士兵的行为,可以自己游泳,爬树。 spearman.setSwimBehavior(new SwimWithBody()); spearman.setClimbBehavior(new ClimbTree()); spearman.swim(); spearman.climb(); System.out.println(" ============================= "); // 另一个玩家建立士兵进入游戏 Soldier cavalryman = new Cavalryman("圆桌骑士"); cavalryman.hit(); // 设置这个士兵的行为,可以骑马过河,但是不可以爬树。 cavalryman.setSwimBehavior(new SwimWithHouse()); cavalryman.setClimbBehavior(new ClimbNoWay()); cavalryman.swim(); cavalryman.climb(); } }
总结一下,请特别注意类之间的关系,关系可以是IS-A(是一个)也可以是HAS-A(有一个)或IMPLEMENTS(实现)。HAS-A关系相当有趣,每一个士兵都有SwimBehavior和ClimbBehavior的行为,并将这些动作交给它们去处理,当将两个类结合起来使用,就是这种组合。这种做法和继承不同的地方在于,行为不是继承来的,而是和适当的行为对象组合来的。这是一个很重要的技巧,骑士是使用了第三个设计原则:
设计原则3
多用组合,少用继承。
使用组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以在运行时动态地改变行为,只要组合的行为对象符合正确的接口标准即可。以上就是策略模式的介绍。