今天我们来看一下策略模式。在真正介绍策略模式之前呢,我们先通过一个非常生动有趣的例子的引入。
我们现在有一个模拟鸭子的程序。
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、现在 我们已经学会了策略模式。哈哈。策略模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。