天天看点

设计模式系列一: 策略模式

序言

目前, 关于设计模式的书籍和博客实在太多, 本系列文章写作的原因更多的在于对自己学习的一个总结以及满足我的分享精神, 另外, 目前公司里面的项目进行重构, 更多的需要关注这方面的内容. 本系列文章只是对设计模式进行大致的介绍, 若您已经是这方面的大牛, 请绕行, 另外, 本系列大部分是参照了”Head FIrst 设计模式”一书中的讲解, 是对其内容的一个概括和一些扩展, 若您想要更少的时间来了解这些设计模式, 恭喜您, 这些文章就能满足你. 好了, 废话不多说, 进入今天的主题: 策略模式.

定义

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

单单这样说, 很难理解具体的含义和使用场景, 接下来, 我们就通过一个具体的例子来进行阐述, 相信通过这个例子, 你将对策略模式有一个较清晰的认识.

示例

现在有这样一个需求, 小明所在的一家游戏公司现在要设计一款游戏鸭子的游戏, 游戏里面有各种各样的鸭子, 一边游泳戏水, 一边呱呱叫. 这时, 小明立马就说, 还不简单, 根据我多年的Java开发经验, 我能很快的写出来, 并且完全符合标准的OO技术. 如下就是他写的代码.

public abstract class Duck {
    protected void quack() {
        System.out.println("呱呱叫");
    }

    protected void swim() {
        System.out.println("游泳");
    }

    protected abstract void display();
}
           
public class BlackDuck extends Duck {
    @Override
    protected void display() {
        System.out.println("黑色鸭子在玩耍");
    }
}
           
public class WightDuck extends Duck {
    @Override
    protected void display() {
        System.out.println("白色鸭子在玩耍");
    }
}
           

这里定义了一个Duck超类, 这个类定义了三个行为, 其中quack和swim为统一的行为, 所有的鸭子都具备同样的行为, 直接在超类中实现, 而每个鸭子都有自己的玩耍行为, 因此需要具体子类去实现. 代码非常简洁明了, 这时领导很高兴, 夸小明能干, 提前完成任务.

但是有一天, 领导为了丰富游戏效果, 就对小明说, 需要会飞的鸭子, 并且再添加一些其他类型的鸭子. 这时, 小明心想, 这还不简单, 我可是”资深Java开发工程师”, 三下五除二就修改好了, 如下为修改的部分:

  1. 在父类中添加fly的方法, 这样所有的子类就都具有这个行为了.
public abstract class Duck {
    protected void quack() {
        System.out.println("呱呱叫");
    }

    protected void swim() {
        System.out.println("游泳");
    }

    protected void fly() {
        System.out.println("自由的飞翔");
    }

    protected abstract void display();
}
           
  1. 增加一个橡皮鸭子
public class RubberDuck extends Duck {
    @Override
    protected void display() {
        System.out.println("橡皮鸭子在玩耍");
    }

    @Override
    protected void quack() {
        System.out.println("吱吱叫");
    }
}
           

正当小明悠哉喝茶时, 测试MM跑来告诉他, 他的程序出了bug, 橡皮鸭子怎么就飞起来了呢? 这时小明意识到当时想的太简单了, 把这个fly()的方法放在父类中, 就会造成所有的子类都具备了这个行为, 这显然是不对的. 这时, 他的脑海里出现了如下两种方案:

1. 在RubberDuck中重写fly()方法, 然后空实现, 这样不就解决了当前的问题了吗, 可是若下一次再来一个木头鸭子, 不会叫也不会飞, 那岂不是要继承两个方法, 都进行空实现了吗….. 这显然不是我们想要看到的, 相应的, 这也暴露了继承的缺点: 就是父类中的行为, 所有的子类都会拥有.

2. 将所有的行为都设计为接口, 这样, 相应得鸭子实现相应的行为, 咋一看, 这样就完美的解决了这些问题, 可是细细想来, 我们要在所有的鸭子中的所有的行为中进行实现, 这样就造成的代码的重复编写, 与我们的初衷背道而驰.

显然, 这两种方案就被否决了, 这时, 他就想到了今天的主角–策略模式.

首先, 我们的原则就是把变化的和不变的分开, 将变化的单独分离出来, 这里, 叫和飞会随着鸭子的不同而不同, 因此, 我们先定义两个接口, FlyBehavior和QuckBehavior, 分别负责飞和叫的行为, 如下:

public interface FlyBehavior  {
    void fly();
}
           
public interface QuackBehavior {
    void quack();
}
           

然后让具体的行为来实现这两个接口, 如下:

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("can not fly");
    }
}
           
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("flying with wings");
    }
}
           
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("橡皮鸭子的吱吱叫");
    }
}
           

这时你可能会问, 这中做法和上面的第二种方案由什么区别吗? 答案是肯定的, 上面的第二种方案是具体的Duck类去实现相应的接口, 在客户端使用时去new具体的Duck类, 这样就造成了代码无法复用, 是面向实现编程, 而非面向接口编程. 而这种方式, 是将具体的行为委托给了具体的行为实现类, 已将和Duck”无关”, 实现解耦, 并且同一种行为可以被多个鸭子复用.

如下为策略模式的具体使用方式:

Duck类:

public abstract class Duck {

    protected FlyBehavior flyBehavior;

    public FlyBehavior getFlyBehavior() {
        return flyBehavior;
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public QuackBehavior getQuackBehavior() {
        return quackBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    protected QuackBehavior quackBehavior;

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

    protected void swim() {
        System.out.println("游泳");
    }

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

    protected abstract void display();
}
           

具体的鸭子类, 以橡皮鸭子为例:

public class RubberDuck extends Duck {

    public RubberDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Squeak();
    }

    @Override
    protected void display() {
        System.out.println("橡皮鸭子在玩耍");
    }

}
           

这样,我们只需要在构造具体鸭子时, 传入具体的行为实现类即可, 不需要做其他的操作, 并且支持动态改变鸭子行为, 只需要在运行时, 通过Duck的set方法将相应的行为设置给它, 就能够实现, 这样就完全符合”开闭原则”.

好了, 讲到这里, 策略模式已经基本讲完了, 相信您已经对这个模式有了相对清晰的了解.

继续阅读