策略模式(Strategy Pattern)
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立与使用算法的客户。
从一个简单的应用说起。
背景
假如我们要设计一款鸭子模拟器。有绿头鸭,红头鸭,玩具鸭,橡皮鸭等。鸭子的行为有呱呱叫,游泳,飞翔等。根据简单的面向对象设计思路。我们先拿出第一版设计图。
第一版
第一版,我们抽象了一个鸭子超类。其他所有类型鸭子从超类继承。超类有呱呱叫(quack)和游泳(swim)方法。
好景不长,很快,我们的新需求出现了,鸭子需要飞行技能,于是我们故技重施,给鸭子超类添加fly()方法。
出现第二版:
第二版
此时,所有的鸭子子类都会继承fly()方法,但问题是,并不是所有但鸭子都能飞。比如橡皮鸭子就不会飞。还发现了其他潜藏的问题,橡皮鸭子也不会呱呱叫。所以橡皮鸭必须将quack()覆盖成“吱吱叫”(squeak)
当涉及“维护”时,为了“复用”(reuse)的目的而使用继承,结局并不完美。
临时方案:各子类根据情况覆盖超类方法
第三版
此时,发现子类中,各种鸭子的差异很大,冗余的“什么都不做”代码有点多。我们试着将fly()方法和quack()方法抽象为接口,让有需要的子类去自行实现这两个接口如何?
第四版
你觉得这个设计如何?
虽然剥离出的两个接口Flyable和Quackable从某种程度解决了一些代码冗余的问题,但造成了新的问题:代码无法复用。每新建一个子类,都需要既继承Duck超类又实现接口。直到。。。。。。。
身骑白马—策略模式
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
是时候把鸭子的行为从Duck类中取出来了。
我们知道 Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要不这两个行为从Duck类中分开,我们要把它们从Duck类中取出来,建立一组新类来代表每个行为。
那么如何设计实现飞行和呱呱叫的行为类呢?
针对接口编程,而不是针对实现编程
为了灵活的设计,从现在开始,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,实际的“实现”不会被绑死在鸭子的子类中。
如图:
“针对接口编程”真正的意思是“针对超类型(supertype)编程。
这样的设计,可以让飞行和呱呱叫的动作被其他对象复用,因为这些行为已经与鸭子类无关了。
而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫的动作“委托”(delegate)别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
代码实现
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-21:57
*/
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void display();
public void performFly(){
flyBehavior.fly(); //委托给行为类
}
public void performQuack(){
quackBehavior.quack(); //委托给行为类
}
public void swim(){
System.out.println("All ducks float,even decoys!!");
}
public void setFlyBehavior(FlyBehavior fb){
//可随时通过该方法修改子类鸭子的飞行行为
flyBehavior=fb;
}
public void setQuackBehavior(QuackBehavior qb){
// 可随时通过该方法修改子类鸭子的鸣叫行为
quackBehavior=qb;
}
}
package strategy;
public interface QuackBehavior {
public void quack();
}
package strategy;
public interface FlyBehavior {
public void fly();
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:01
*/
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I cat't fly");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:21
*
* 建立一个利用火箭动力的飞行行为
*/
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with a rocket!!");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-21:59
*/
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm Flying!!!");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:01
*/
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("strategy.Quack");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:03
*/
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Slience >>");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:08
*/
public class MallarDuck extends Duck {
public MallarDuck(){
quackBehavior=new Quack(); //TODO 这里不够灵活
flyBehavior=new FlyWithWings();//TODO 这里不够灵活
}
@Override
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:18
*/
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");
}
}
package strategy;
/**
* @author zhangjinglong
* @date 2019-11-05-22:11
*
* 测试类
*/
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallard=new MallarDuck();
mallard.performFly();
mallard.performQuack();
Duck model=new ModelDuck();
model.performFly();//模型鸭默认不会飞
model.setFlyBehavior(new FlyRocketPowered());//给模型鸭设定火箭动力的飞行行为
model.performFly();//模型鸭 动态改变了飞行行为
}
}