策略模式(HeadFirst)

此文章总结于 Head First设计模式一书的策略模式。

故事背景

Job上班的公司做了一个模拟鸭子的游戏:SimUDuck。游戏中会出现各种鸭子,一边游戏戏水,一边呱呱叫。系统设计了一个鸭子超类,并让各种鸭子继承这个超类。一开始的系统设计如下图:
image.png

需求变更

现在增加了一个让鸭子会飞的需求。Job决定在Duck类中加入一个fly()的方法,然后所有的鸭子都会继承fly()
image.png

但是可怕的问题发生了

Job忽略了一件事情:并非所有的鸭子都会飞,在超类上加上新的行为,会使得某些不适合改行为的子类也具有该行为。
SimUDuck程序中出现了一个无生命的会飞的东西( “橡皮鸭子” 在屏幕上飞来飞去)。
对代码局部修改,影响的层面可不只是局部。

Joe想到了继承

为了解决问题,Joe开始了思考:我可以把橡皮鸭类中的fly()方法覆盖掉。
image.png
可是以后加入诱饵鸭(DecoyDuck)又会如何?诱饵鸭是木头假鸭不会飞也不会叫。
image.png

利用接口改进如何?

Joe认识到继承可能不是答案,Joe知道规格会常常变化,每当有新的鸭子子类出现,他就要被迫检查并可能需要覆盖fly()和quark()…这简直就是无穷无尽的噩梦。

Job想到:我可以把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来只有会飞的鸭子才实现接口。同样的,设计一个“Quackable接口”,因为不是所有的鸭子都会叫。
image.png

但是这样又会造成代码无法复用的问题。每一个会飞的子类都要实现“Flyable接口”。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化。如果Duck类有很多子类实现了飞行的行为,要稍微修改一下飞行的行为,那将变得很麻烦。

分开变化和不会变化的部分

设计原则: 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。

我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。
image.png

在此,我们有两个接口,FlyBehavior和QuackBehavior,还有它们对应的实现类,负责实现具体的行为:
image.png

整合鸭子的行为

整合鸭子的行为关键在于,鸭子现在会将飞行和呱呱叫的动作“委托”给别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。

具体做法如下:

  1. 在Duck类加入两个接口类型的成员变量,“flyBehavior”和“quackBehavior”
public  abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
}
  1. 实现performQuack()方法,实现委托给别人处理
public void performQuack(){
    quackBehavior.quack();
}
  1. 提供set方法可以在运行时候方便修改行为
public void setQuackBehavior(QuackBehavior quackBehavior) {
    this.quackBehavior = quackBehavior;
}

最终的设计

image.png

代码实现

飞行行为:

public interface FlyBehavior {
    void fly();
}

public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("没有飞行能力");
    }
}


public class FlyWithWings implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("我也会飞了");
    }
}

public class FlyRocketPowered implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("利用火箭动力飞行");
    }
}

叫的行为:

public interface QuackBehavior {
    void quack();
}


public class MuteQuack implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("不会叫");
    }
}

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  abstract class Duck {

    //接口类型
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public abstract void display();

    public void performFly(){
        flyBehavior.fly();
    }

    public void performQuack(){
        quackBehavior.quack();
    }

    public void swim(){
        System.out.println("游啊游");
    }

    //提供set方法,可以在运行时修改飞行行为
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }
    //提供set方法,可以在运行时修改叫的行为
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}

具体的模型鸭:

public class ModelDuck extends Duck{

    //在构造方法初始化默认的行为,也可以通过构造参数传递。
    public ModelDuck(){
        
        //一开始模型鸭不会飞
        flyBehavior = new FlyNoWay();
        //可以呱呱叫
        quackBehavior = new Quack();
    }


    @Override
    public void display() {
        System.out.println("模型鸭");
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        //模型鸭
        Duck modelDuck = new ModelDuck();
        //模型鸭一开始不会飞
        modelDuck.performFly();//输出:没有飞行能力
        modelDuck.performQuack();//输出:我能大声叫:呱呱呱
        //修改飞的行为
        modelDuck.setFlyBehavior(new FlyRocketPowered());
        //具有飞行能力
        modelDuck.performFly();//输出:利用火箭动力飞行
    }
}

控制台输出:
    没有飞行能力
	我能大声叫:呱呱呱
	利用火箭动力飞行

策略模式的定义

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

结构

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。(上面的QuackBehavior和FlyBehavior)
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。(QuackBehavior和FlyBehaviord的实现类)
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。(Duck类)

优缺点

优点:

  • 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值