今天我们来看一下策略模式。在真正介绍策略模式之前呢,我们先通过一个非常生动有趣的例子的引入。
我们现在有一个模拟鸭子的程序。
1、鸭子父类如下:
public abstract class Duck { /** * 呱呱叫行为(所有鸭子都会) */ public void quack() { System.out.println("呱呱叫"); } /** * 游泳行为(所有鸭子都会) */ public void swim() { System.out.println("游泳"); } /** * 每个鸭子额外观都不同 所以display是抽象方法 */ abstract void display(); }
MallardDuck子类
public class MallardDuck extends Duck { /** *头是绿色的 */ @Override void display() { System.out.println("外观是绿头的鸭子"); } }
RedheadDuck子类
public class RedheadDuck extends Duck { /** * 外观是红头 */ @Override void display() { System.out.println("外观是红头的鸭子"); } }
2、现在呢,有一个需求,需要让鸭子具备会飞的功能。所以,理所当然的就给Duck父类添加上了一个fly()方法;但是这里问题出现了,一些不具备会飞行为的鸭子也具备了“飞”的行为(比如,我们现在有一个橡皮鸭子,它不会飞,并且不会呱呱叫,只会吱吱叫)。
3、这时候,我们想到,可以在重写橡皮鸭子的fiy()方法,让它什么都不做,并重写quack()方法,让它吱吱叫。代码如下:
public class RubberDuck extends Duck { /** * 不会飞 什么都不做 */ @Override public void display() { System.out.println(""); } /** * 吱吱叫(不会 呱呱叫) */ @Override public void quack() { System.out.println("吱吱叫"); } }
4、可是,如果以后我们加入诱饵鸭(DecoyDuck),它是一只假鸭子,不会飞也不会叫。难道我们要接着重写方法吗?
5、利用接口如何?那好,加入我们利用接口,把fly()方法从超类中取出来,放进“Fiyable”接口中。这么一来,只有会飞的鸭子才实现此接口,同样的方式,也可以设计一个“Quackable”接口,因为并不是所有的鸭子都会叫。
用这种方法确实解决了一部分的问题(不会再有会飞的橡皮鸭子),但是却造成代码无法被复用(因为每一个子类都需要实现接口的方法)。
现在我们想一下,会不会有一种对既有的代码影响最小的方式来修改程序?
6、现在我们有一个设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。以此让我们的代码变化更少,系统更加有弹性。
7、为了让程序有弹性,并且我们还想能够指定行为到鸭子的实例,让鸭子可以在“运行时”改变“鸭子”的行为。从现在开始,鸭子的行为将被放在分开的类中,此类专门提供某行为的接口的实现。这样,鸭子就不再需要知道行为的实现细节。(设计原则:针对接口编程,而不是针对实现编程)。
所以,我们先声明两个接口,FlyBehavior和QuackBehavior,而行为的每个实现都将实现其中的一个接口。直接上代码。
FlyBehavior(“飞”行为接口):
public interface FlyBehavior { /** * 飞(所有的新的飞行类都必须实现fly方法) */ void fly(); }
用翅膀飞类:
public class FlyWithWings implements FlyBehavior { @Override public void fly() { System.out.println("用翅膀来飞行"); } }
不会飞类:
public class FlyNoWay implements FlyBehavior { /** * 不会飞 */ @Override public void fly() { System.out.println(""); } }
QuackBehavior(“叫”行为接口):
public interface QuackBehavior { /** * 呱呱叫行为(每一个新的叫行为都必须实现quack方法) */ void quack(); }
呱呱叫行为类:
public class Quack implements QuackBehavior { /** * 呱呱叫 */ @Override public void quack() { System.out.println("呱呱叫"); } }
吱吱叫行为类:
public class Squeak implements QuackBehavior { /** * 吱吱叫 */ @Override public void quack() { System.out.println("吱吱叫"); } }
不会叫类:
public class MuteQuack implements QuackBehavior { /** * 不会叫 */ @Override public void quack() { System.out.println(""); } }
8、下面,我们来重新改写鸭子父类:
public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior; /** * 游泳行为(所有鸭子都会) */ public void swim() { System.out.println("游泳"); } /** * 每个鸭子额外观都不同 所以display是抽象方法 */ public abstract void display(); /** * 飞行为 */ public void performFly() { flyBehavior.fly(); } /** * 呱呱叫行为 */ public void performQuack() { quackBehavior.quack(); } /** * 设置飞行为 * @param flyBehavior */ public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } /** * 设置叫行为 * @param quackBehavior */ public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } }
重写绿头鸭类:
public class MallardDuck extends Duck { public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); } /** *头是绿色的 */ @Override public void display() { System.out.println("外观是绿头的鸭子"); } }
通过我们的改写,会发现,我们的绿头鸭子在new一个对象的时候,会呱呱叫,会用翅膀飞,我们还可以运行时改写它的飞行为的方式,可以把它设为不会飞,或者我们在写一个FlyBehavior的实现类,用另外一种方式来飞,这些都是可以的。
我们写一个测试类:
public static void main(String[] args) { Duck mallardDuck = new MallardDuck(); mallardDuck.performFly(); mallardDuck.setFlyBehavior(new FlyNoWay()); mallardDuck.performFly(); }
执行结果:
9、最后,我们会发现,如果再来新的需求,我们不需要改变原来的任何代码,比如来了一只会用火箭飞的鸭子,那我们只需要新写一个FlyBehavior的实现类,然后在构造器里写出来就行了。对修改关闭,对扩展开放。
10、现在 我们已经学会了策略模式。哈哈。策略模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
最后来个总结:
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。