策略模式(HeadFirst)

2023-03-03  本文已影响0人  来一斤小鲜肉

此文章来源于 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();//输出:利用火箭动力飞行
    }
}

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

策略模式的定义

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

结构

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

优缺点

优点:

缺点:

上一篇 下一篇

猜你喜欢

热点阅读