策略模式
策略模式的设计原则
一、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
二、针对接口编程【指的是针对超类型(supertype)】,而不是针对实现编程。
策略模式是对象行为型模式,其意图是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
策略模式的结构
参与者
- Strategy——定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。
- ConcreteStrategy——具体策略,以Strategy接口实现某具体的算法。
- Context——用一个ConcreteStrategy对象来配置;维护一个Strategy对象的引用;可定义一个接口来让Strategy访问它的数据。
协作
- Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
- Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context;这样,客户与Context交互。通常有一系列的ConcreteStrategy可供客户从中选择。
使用场景
模拟鸭中的飞行行为。在模拟鸭中,有一些需要具备飞行行为、而另一些并不具备飞行行为;并且如何让不具备飞行行为的诱饵鸭具备飞行行为。根据需求,我们将飞行行为单独提取出来做为一个接口。在具体实现类中去实现不同的需求。
//FlyBehavior
package headfirst.strategy;
public interface FlyBehavior {
public void fly();
}
//FlyNoWay
package headfirst.strategy;
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
//FlyRocketPowered
package headfirst.strategy;
public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
//FlyWithWings
package headfirst.strategy;
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
//Duck模拟鸭基类
package headfirst.strategy;
public class Duck {
FlyBehavior flyBehavior;
public Duck() {
}
public void setFlyBehavior (FlyBehavior fb) {
flyBehavior = fb;
}
public void performFly() {
flyBehavior.fly();
}
}
//诱饵鸭
package headfirst.strategy;
public class DecoyDuck extends Duck {
public DecoyDuck() {
setFlyBehavior(new FlyNoWay());
}
}
// 绿头鸭
package headfirst.strategy;
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings();
}
}
//模拟鸭测试程序
package headfirst.strategy;
public class MiniDuckSimulator {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MallarDuck mallard = new MallarDuck();
DecoyDuck decoy = new DecoyDuck();
mallard.performFly();
decoy.performFly();
decoy.setFlyBehavior(new FlyRocketPowered()); //让诱饵鸭实现飞行行为
decoy.performFly();
}
}
程序运行后的输出结果为:
I'm flying
I Can't fly
I'm flying with a rocket
效果
- 相关算法系列——Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
- 一个替代继承的方法——继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这将会将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类, 它们之间的唯一差别是它们所使用的算法或行为。将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
- 避免使用多重条件(if-else)语句——多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面。
- 实现的选择——Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求从不同的策略中进行选择。
- 客户必须了解不同的Strategy——本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时, 才需要使用Strategy模式。
- Strategy和Context之间的通信开销——无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题, 那么将需要在Strategy和Context之间更进行紧密的耦合。
- 增加了对象的数目——Strategy增加了一个应用中的对象的数目。有时你可以将Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的Strategy不应在各次调用之间维护状态。