策略模式——运筹帷幄

作者: 徐同学呀 | 来源:发表于2018-06-06 11:27 被阅读26次

    一、定义

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

    二、抛砖引玉

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

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

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

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

    //妙计接口:
    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.haomeiwen.com/subject/jthbsftx.html