策略模式的定义
- 策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Polic
y)。
策略模式是一种对象"行为型"模式。
策略模式的使用场景
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
- 需要安全的封装多种同一类型的操作时。
- 出现同一抽象类有多个子类,而又需要使用if-else 或者 switch-case 来选择具体子类时。
策略模式的UML类图
角色介绍:
- Context : 用来操作策略上下文环境。
- Stragety : 策略的抽象。
- ConcreteStragetyA , ConcreteStragetyB : 具体的策略实现。
策略模式示例:
- 假设有一个模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个游戏的内部设计了一个鸭子超类Duck,并让各种鸭子继承此超类。
让鸭子能飞
去年,公司的竞争力加剧,公司主管认为该是创新的时候了。主管认为,此模拟程序需要会飞的鸭子,将竞争者抛在后面。
改进继承
Joe认识到继承可能不是一个好的解决办法,因为他刚刚拿到来自主管的备忘录,希望以后每六个月更新产品(至于更新办法,他们还没想到)。Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检视并可能需要覆盖fly()和quack().....这简直是无穷尽的噩梦。所以,他需要一个更清晰的方法,让某些(而不是全部)鸭子类型可飞或可叫。
第一个设计原则
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这个概念很简单,几乎是每个设计模式背后的精神所在,所有的模式都提供了一套方法让系统中的某部分改变不会影响其它部分。
1. 分开变化和不会变化的部分
-
就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常。现在,为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck类),一个是“fly”相关的,一个是“quack”相关的,每一组类将实现各自的动作。比如说,我们可能有一个类实现“呱呱叫”,另一个类实现“叽叽叫”,另一个类实现“安静”。
2. 设计鸭子的行为
- 如何设计那组实现飞行和呱呱叫的行为的类呢?
我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们应该在鸭子类中包含设定行为的方法,这样可以实现在“运行时”动态地“改变”鸭子的飞行行为。
第二个设计原则
设计原则:针对接口编程,而不是针对实现编程。
针对接口编程,而不是针对实现编程,举例:
// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark();
// 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound();
// 更棒的是,子类实例化的动作不再需要在代码中硬编码,
// 例如new Dog(),而是“在运行时才指定具体实现的对象”
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();
所以我们可以利用接口代表行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中一个接口。
所以这次鸭子类不会负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就成为“行为”类。
这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现。这两种做法都是依赖于“实现”,我们被“实现”绑得死死的,没办法更改行为。
在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。
3.实现鸭子的行为
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子无关了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响有使用到飞行行为的鸭子类。
4. 集成鸭子的行为
- 鸭子现在会将飞行和呱呱叫的动作,委托(delegate)别人处理,而不是使用定义在自己类(或子类)内的方法。
- 首先,在鸭子中加入两个实例变量, 分别为FlyBehavior与QuackBehavior,声明为接口类型(而不是具体类实现类型),每个变量会利用多态的方式在运行时引用正确的行为类型(例如:FlyWithWings、Squeak . . . 等)。
-
我们也必须将Duck类与其所有子类中的fly() 与quack( ) 移除,因为这些行为已经被搬移到FlyBehavior与QuackBehavior类中了。我们用performFly()和performQuack()取代Duck类中的fly()与quack()。
每一个鸭子都有一个FlyBehavior和QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。
5.动态的设定行为
在Duck类中,加入两个新方法:
public void setFlyBehavior(FlyBehavior fb){
flyBehavior=fb;
}
public void setQuackBehavior(QuackBehavior qb){
quackBehavior=qb;
}
从此以后,我们可以“随时”调用这两个方法改变鸭子的行为。
代码示例:
Duck.java
/**
* 抽象鸭子超类
*/
public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void swim() {
System.out.println("All ducks float, even decoys!");
}
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
}
DecoyDuck.java
/**
* 诱饵鸭
*/
public class DecoyDuck extends Duck {
public DecoyDuck() {
setFlyBehavior(new FlyNoWay());
setQuackBehavior(new MuteQuack());
}
@Override
public void display() {
System.out.print("I'm a duck Decoy");
}
}
ModelDuck.java
/**
* 模型鸭
*/
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a model duck");
}
}
MallardDuck.java
/**
* 绿头鸭
*/
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
RubberDuck.java
/**
* 橡皮鸭
*/
public class RubberDuck extends Duck{
public RubberDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
@Override
public void display() {
System.out.println("I'm a rubber duckie");
}
}
RedHeadDuck.java
/**
* 红头鸭
*/
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a real Red Headed duck");
}
}
FlyBehavior.java
/**
* 飞行行为接口
*/
public interface FlyBehavior {
void fly();
}
FlyRocketPowered.java
/**
* 火箭动力飞行
*/
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
FlyNoWay.java
/**
* 不会飞
*/
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("I can't fly");
}
}
FlyWithWings.java
/**
* 实现鸭子飞行
*/
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.print("I'm flying!!");
}
}
QuackBehavior.java
/**
* 叫的行为接口
*/
public interface QuackBehavior {
void quack();
}
Quack.java
/**
* 嘎嘎叫
*/
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack");
}
}
FakeQuack.java
/**
* 假嘎嘎叫
*/
public class FakeQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Qwak");
}
}
Squeak.java
/**
* 吱吱叫
*/
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak");
}
}
MuteQuack.java
/**
* 禁音
*/
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Silence >>");
}
}
测试程序 MiniDuckSimulator.java
/**
* 迷你鸭子模拟器测试程序
*/
public class MiniDuckSimulator {
public static void main(String[] args){
MallardDuck mallard = new MallardDuck();
RubberDuck rubberDuckie = new RubberDuck();
DecoyDuck decoy = new DecoyDuck();
Duck model = new ModelDuck();
mallard.performQuack();
rubberDuckie.performQuack();
decoy.performQuack();
model.performQuack();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
测试结果:
Quack
Squeak
<< Silence >>
Quack
I'm flying with a rocket
总结
-
所有鸭子从Duck继承,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。
“有一个”(has a)可能比“是一个”(is a)更好
有一个关系相当有趣:每一鸭子都有一个FlyBehavior且有一个QuackBehavior,让鸭子将飞行和呱呱叫委托它们代为处理。
如果将两个类结合起来使用(如同本例),这就是组合(Composition)。这种作法和继承不同的地方在于:鸭子的行为不是继承而来,而是和适当的行为对象组合而来。
第三个设计原则
设计原则:
- 多用组合,少用继承
- 使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态地改变行为。
优点
- 策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。这个模式很好的演示了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的可扩展性。
- 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
- 结构清晰明了,使用简单直观。
- 耦合度相对而言较低,扩展方便。
- 操作封装也更为彻底,数据更为安全。
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 随着策略的增加,策略模式会造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
Android源码中的策略模式实现
- 时间插值器