策略模式——运筹帷幄

一、定义

定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。

二、抛砖引玉

这个通俗易懂的小栗子,原版出自《设计模式之禅(第二版)》。

三国情景再现:
诸葛亮在刘备去东吴招亲之前,特授予伴郎赵云三个锦囊,说是按天机拆开解决棘手问题。

这三个妙计分别是:找乔国老帮忙(也就是走后门了),求吴国太放行(开绿灯)以及孙夫人断后。

接下来我们模拟这三个妙计的使用场景。

//妙计接口:
public interface Strategy {
    void operate();
}


//妙计具体实现(一)——乔国老开后门
public class BackDoor implements Strategy {
    @Override
    public void operate() {
        System.out.println("找乔国老帮忙,让吴国太给孙权施加压力");
    }
}
//妙计具体实现(二)——求吴国太放行(开绿灯)
public class GivenGreenLight implements Strategy {
    @Override
    public void operate() {
        System.out.println("求吴国太开绿灯,放行");
    }
}

//妙计具体实现(三)——孙夫人断后
public class BlockEnemy implements Strategy {
    @Override
    public void operate() {
        System.out.println("孙夫人断后,挡住追兵");
    }
}

//装锦囊的袋子
public class Context {
    private Strategy strategy;
    public Context(Strategy strategy){
        this.strategy = strategy;
    }

    //使用计谋
    public void operate(){
        this.strategy.operate();
    }
}

//赵云
public class ZhaoYun {
    public static void main(String[] args) {
        Context context = null;
        System.out.println("---刚刚到吴国的时候拆第一个锦囊");
        context = new Context(new BackDoor());
        context.operate();

        System.out.println("---刘备乐不思蜀,拆开第二个");

        context = new Context(new GivenGreenLight());
        context.operate();

        System.out.println("---孙权的小兵追了,拆第三个");
        context  = new Context(new BlockEnemy());
        context.operate();
    }
}

//最后运行结果

---刚刚到吴国的时候拆第一个锦囊
找乔国老帮忙,让吴国太给孙权施加压力
---刘备乐不思蜀,拆开第二个
求吴国太开绿灯,放行
---孙权的小兵追了,拆第三个
孙夫人断后,挡住追兵

栗子很简单,也很明了。不再做过多解释,倘若看不太懂,说明你需要补充一下java基础。

策略模式属于行为型模式,在实际开发中,我们要分析一个事物哪些行为是不可变的,哪些行为是可变的。可变行为的开发,日后开发要做到易维护、易扩展。

前人为我们总结出了策略模式,将可变行为抽象,接口化(符合迪米特原则)行为分类定义接口(符合接口分离原则),之后对应实现具体的行为,日后需要扩展时就新建一个类实现行为接口(符合开闭原则)。

三、第二个栗子:

接下来再看一个鸭子的栗子,鸭子的种类很多,有的会飞,飞得高飞得久(所谓飞得好),反之就是飞得差,有的甚至不会飞,就不需要这个行为;有的叫声是咕咕,有的叫声嘎嘎。

首先我们把飞和叫声分别抽象

飞行为抽象

public interface FlyBehavior {
    void fly();
}

//飞得好
public class GoodFlyBehavior implements FlyBehavior {
    private GoodFlyBehavior(){

    }
    public  static  GoodFlyBehavior getInstantiation(){
        return new GoodFlyBehavior();
    }
    @Override
    public void fly() {
        System.out.println("good-----fly");
    }
} 

//飞得差
public class BadFlyBehavior implements FlyBehavior{
    private BadFlyBehavior(){

    }
    //忽略我这里用了反射
    public static BadFlyBehavior getInstantiation(){
        return new BadFlyBehavior();
    }
    @Override
    public void fly() {
        System.out.println("bad----fly");
    }
}



叫声行为抽象:

public interface QuackBehavior {
    void quack();
}

public class GaGaQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("gaga---quack");
    }
}

public class GuGuQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("gugu---quack");
    }
}

接下来我们开始造鸭子了

//首先造一只抽象的鸭子
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    public Duck(){

    }

    public void fly(){
        if (flyBehavior!=null){
            flyBehavior.fly();
        }
    }

    public void quack(){
        if (quackBehavior!=null){
            quackBehavior.quack();
        }
    }
    public abstract void eat();

    public void swim(){
        System.out.println("小鸭子swim--");
    }
}

//绿头鸭飞得好,嘎嘎叫
public class GreenHeadDuck extends Duck {

    public GreenHeadDuck(){
        flyBehavior = GoodFlyBehavior.getInstantiation();
        quackBehavior = new GaGaQuackBehavior();
    }
    @Override
    public void eat() {
        System.out.println("吃鱼");
    }


}
//红头鸭飞得差,咕咕叫
public class RedHeadDuck extends Duck {

    public RedHeadDuck(){
        flyBehavior = BadFlyBehavior.getInstantiation();
        quackBehavior = new GuGuQuackBehavior();
    }
    @Override
    public void eat() {
        System.out.println("吃鱼---");
    }
}

//蓝头鸦,不会飞,咕咕叫
public class BlueHeadDuck extends Duck {
    public BlueHeadDuck(){
        quackBehavior = new GuGuQuackBehavior();
    }
    @Override
    public void eat() {
        System.out.println("坐着吃");
    }
}


//测试:
public class test1 {
    public static void main(String[] args) {
        Duck green = new GreenHeadDuck();
        green.fly();
        green.quack();
        System.out.println("---------------------------");
        Duck red = new RedHeadDuck();
        red.fly();
        red.quack();
        System.out.println("---------------------------");
        Duck blue = new BlueHeadDuck();
        blue.fly();// 虽然调了fly,但是没打印出什么,看方法逻辑上此方法是空的。
        blue.quack();
    }
}

--------------绿头鸭-------------
good-----fly
gaga---quack
--------------红头鸭-------------
bad----fly
gugu---quack
--------------蓝头鸭-------------
gugu---quack

可以看得出策略模式使用的就是面向对象的继承和多态机制。

策略模式的优点:

  1. 策略可以自由切换
  2. 避免使用多重条件判断
  3. 扩展性好

缺点:

  1. 策略类数量过多
  2. 所有的策略类都需要对外暴露。上层模板必须知道有哪些策略,然后才能决定使用哪个策略,这与迪米特法则是相违背的,
    我只是想使用个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?
    这是原装策略模式的一个缺点,幸运的是我们可以使用其他模式来修正这个缺陷,如工厂模式,代理模式或享元模式。

使用场景:

  1. 多个类只有在算法或者行为上稍有不同的场景
  2. 算法需要自由切换的场景
  3. 需要屏蔽算法规则的场景

四、再来举个计算加减的栗子:

同样出自《设计模式之禅(第二版)》的改编。

一个计算器,有两个功能,两个数相加,两个数相减。

public interface Calculator {
    int exec(int a,int b);
}

public class Add implements Calculator {
    @Override
    public int exec(int a, int b) {
        return a+b;
    }
}

public class Sub implements Calculator {
    @Override
    public int exec(int a, int b) {
        return a-b;
    }
}

public class Context {
    private Calculator cal = null;
    public Context(Calculator cal){
        this.cal = cal;
    }

    public int exec(int a,int b){
        return  this.cal.exec(a,b);
    }

}

public class test {
    public static void main(String[] args) {
        Context context = new Context(new Add());
        int result = context.exec(10,10);
        System.out.println(result);

        Context context1 = new Context(new Sub());
        int result1 = context1.exec(20,10);
        System.out.println(result1);
    }
}

来个高端的操作:

public enum Calculator {
    //+
    ADD ("+"){
        public int exec(int a,int b){
            return a+b;
        }
    },
    SUB("-"){
        public int exec(int a,int b){
            return a-b;
        }
    };
    String value = "";

    //定义成员值类型
    private Calculator(String _value){
        this.value = _value;
    }

    //获得枚举成员的值
    public String getValue(){
        return this.value;
    }

    //声明一个抽象函数
    public abstract int exec(int a,int b);
}


public class Client {
    public static void main(String[] args) {
        int add_result = Calculator.ADD.exec(10,20);
        int sub_result = Calculator.SUB.exec(20,10);

        System.out.println(add_result);
        System.out.println(sub_result);
    }
}

是不是感觉很清爽,这就叫策略枚举

策略枚举,是一个非常优秀和方便的模式,但是它受枚举类型的限制,每个枚举项都是public、final、static的,扩展性受了一定的约束,所以在系统开发中,策略枚举一般担当不经常发生变化的角色。

需要注意:

如果系统中的一个策略家族的具体策略数量超过了4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会成为一个烫手山芋。

策略模式的精华远不止这些,还待实际开发中运用和体会。

源码地址:
https://gitee.com/stefanpy/DesignPattern

    原文作者:韵呀
    原文地址: https://www.jianshu.com/p/4821c907d0a8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞