美文网首页Android知识架构设计与重构程序员
回馈顾客, 活动搞起---策略模式

回馈顾客, 活动搞起---策略模式

作者: anly_jun | 来源:发表于2016-11-22 09:55 被阅读537次
    cover

    前情提要

    上集讲到, 小光引入了饮料机(工厂方法模式)改进了光氏饮品的生产过程. 现在如果要新上什么饮品, 改变配方什么的, 都很简单了, 直接增加一个饮料机, 或是替换/拿掉一个饮料机就可以了. 表妹再也不抱怨了.

    小光也找了些饮料厂商拿到了一些试喝的饮料新品. 心想, 正好临近感恩节, 圣诞节, 双十二啥的, 我可以拿这些饮料新品来做些活动啊, 感恩下新老顾客啊... 这些新品小光可是自己亲身试喝过的, 绝对好喝, 小光不做奸商, :)

    所有示例源码已经上传到Github, 戳这里

    活动策划

    小光以其独特的码农生意人思维(我也不知道这是什么...), 很快想出了几条活动方案:

    • 即日起, 到感恩节(11/24)那天, 所有饮品6折优惠.
    • 双十二当天, 满12立减2元.
    • 12月20号到圣诞节(12/25), 买热干面+饮料套餐送大苹果.

    小光想出这些活动方案后, 屁颠屁颠去拿给表妹看. 没曾想, 表妹一脸不愉快, 这么多方案, 还这么负责, 我怎么记得住...(单细胞的表妹).

    怎么办了, 小光可不想放弃自己好不容易想出的这些方案, 而且活动方案肯定会因为是不同节日而有所改变嘛.

    解决之道

    很快, 小光就想到了解决办法, 他将三种活动方案的算法做好, 内置在收银台. 在不同的日子里选用不用的算法策略.

    "我真是个天才, 哈哈哈哈", 小光想着都快笑出声了...

    照例, 收银员无需关注是什么具体的算法, 故而抽象出一个父级接口:

    public interface ActivityStrategy {
    
        String getActivityPrice();
    }
    

    每个方案对应一个算法策略:

    // 感恩节活动算法
    public class ThanksGivingDayStrategy implements ActivityStrategy {
    
        @Override
        public String getActivityPrice() {
            // 经过一系列算法
            return "(感恩节)所有饮品一律5折";
        }
    }
    
    // 双十二算法
    public class DoubleTwelveDayStrategy implements ActivityStrategy {
    
        @Override
        public String getActivityPrice() {
            // 经过一系列算法
            return "(双十二)满12立减2元";
        }
    }
    
    // 圣诞节算法
    public class ChristmasStrategy implements ActivityStrategy {
    
        @Override
        public String getActivityPrice() {
            // 经过一系列算法
            return "(圣诞节)买热干面+饮品套餐, 送大苹果一个";
        }
    }
    
    // 默认算法(注意这个, 稍后的扩展阅读会说下这个Default实现的意义)
    public class DefaultActivityStrategy implements ActivityStrategy {
        @Override
        public String getActivityPrice() {
            // 什么都不做
            return "没有活动";
        }
    }
    

    支持各种活动策略算法的收银台:

    // 收银台
    public class Checkstand {
    
        private ActivityStrategy mActivityStrategy;
    
        public Checkstand() {
            mActivityStrategy = new DefaultActivityStrategy();
        }
    
        public Checkstand(ActivityStrategy activityStrategy) {
            this.mActivityStrategy = activityStrategy;
        }
    
        public void setActivityStrategy(ActivityStrategy activityStrategy) {
            this.mActivityStrategy = activityStrategy;
        }
    
        public void printBill() {
            System.out.println("本次账单活动:" + mActivityStrategy.getActivityPrice());
        }
    }
    

    投入使用

    活动方案算法和收银台完工之后, 小光立马投入了使用:

    public class XiaoGuang {
    
        public static void main(String[] args) {
    
            // 收银台, 默认
            Checkstand checkstand = new Checkstand();
            checkstand.printBill();
    
            // 感恩节期间
            checkstand.setActivityStrategy(new ThanksGivingDayStrategy());
            checkstand.printBill();
    
            // 双十二
            checkstand.setActivityStrategy(new DoubleTwelveDayStrategy());
            checkstand.printBill();
    
            // 圣诞节期间
            checkstand.setActivityStrategy(new ChristmasStrategy());
            checkstand.printBill();
        }
    }
    

    结果, 也正如小光预料的:

    本次账单活动:没有活动
    本次账单活动:(感恩节)所有饮品一律5折
    本次账单活动:(双十二)满12立减2元
    本次账单活动:(圣诞节)买热干面+饮品套餐, 送大苹果一个
    

    活动一经推出, 顾客果然是比以前更多了...
    大家还对小光新推出的那些试喝饮料赞不绝口, 都觉得味道不错, 还很着很有意思的名字...

    故事之后

    照例, 故事之后, 我们用UML类图来梳理下上述的关系(关注收银台与活动策略算法之间的关系):

    大家可能已经看出端倪了, 没错, 这就是策略模式.

    策略模式(Strategy Pattern):
    定义一组算法, 并将每一个单独算法封装起来, 让它们可以相互替换.

    策略模式让算法独立于使用它的客户而变化, 例如如果明年小光的双十二活动改变了, 只需单独修改这个DoubleTwelveDayStrategy即可, 客户类(收银台Checkstand)无需改变, 也无需关注每个算法的具体实现.

    扩展阅读一

    实际上策略模式也还是利用抽象, 封装, 继承, 多态的面向对象特性, 来达到封装变化, 解耦合的. 典型的开闭原则的实践.

    另外, 眼尖的同学可能看到, 貌似这个类图似曾相识啊. 前面讲的简单工厂工厂方法中的类图与此极其相似:

    比较上图三个模式的红框部分, 我们可以发现, 相当一致. 在此明确下三者的关系与区别:

    1. 首先简单工厂工厂方法创建型的模式, 而策略模式行为型的模式.
    2. 所谓创建型就是说用来生产对象的, 注重的生产(new)这个部分, 用创建型的模式来代替直接new一个实例, 更多是想将直接的实例依赖通过不同的方法转化接口依赖.
    3. 所谓行为型模式更多是描述一种行为, A使用B, 怎么使用的这个关系上.

    实际上, 在上个工厂方法的故事中, 我们就已经使用到了策略模式.

    表妹选择不同的饮料机来那饮料, 这个行为实际上就是一个策略模式的体现, 回顾下表妹的代码:

    public class Cousins {
    
        private IBeverageMachine mBeverageMachine;
    
        private void setBeverageMachine(IBeverageMachine machine) {
            this.mBeverageMachine = machine;
        }
    
        private Drink takeDrink() {
            if (mBeverageMachine == null) throw new NullPointerException("Should set Beverage Machine firstly.");
    
            return mBeverageMachine.makeDrink();
        }
    
        public static void main(String[] args) {
    
            Cousins cousins = new Cousins();
    
            // for A
            cousins.setBeverageMachine(new OrangeJuiceMachine());
            Drink drink = cousins.takeDrink();
            System.out.println(drink);
    
            // for B
            cousins.setBeverageMachine(new CokeMachine());
            System.out.println(cousins.takeDrink());
    
            // for D
            cousins.setBeverageMachine(new MilkTeaMachine());
            System.out.println(cousins.takeDrink());
        }
    }
    

    和我们这个收银台(Checkstand)是一样一样的, 上例中的模式使用实际可以理解成是这样:

    • 蓝色部分是工厂方法模式的使用, 作用在于生产出不同的饮品.
    • 红色部分是策略模式的使用, 作用在于让表妹根据实际情况选择不同的饮料机.

    所以说模式的运用, 往往不是简单而单一, 很多时候是很多模式合在一起的.

    扩展阅读二

    在展示本例策略模式的UML类图时, 我们将DefaultActivityStrategy类标记成红色了, 这是为什么呢?

    是因为这里我们用的这个DefaultActivityStrategy实际上也是一种设计模式的体现. 这个模式不在GoF的23中设计模式内, 但是绝对是一个很常用, 很实用的模式 --- 空对象模式.

    空对象模式(Null Object Pattern):
    用一个空(什么都不做的)对象来代替NULL.

    空对象模式是一个很简单的设计模式, 也可以看成是一种编码习惯. 它小但是作用大:

    • 使用空对象模式可以减少很多我们对于对象是否为空的判断. 例如本例中, 如果Checkstand的无参构造函数我们没有new一个空对象, 那么后续的对于Checkstand实例各种调用我们可能就需要判断其mActivityStrategy是否为空. 如果遗漏, 很有可能导致null pointer异常.
    • 另外对于一些可以链式调用的对象, 如果我们要每次都判断是否为空会很影响我们的链式调用.

    空对象模式经常会用来作为策略模式算法族中的一个, 来提供空策略.

    扩展阅读三

    策略模式由于其优秀的对外扩展性和对内封装性, 在一些SDK或是优秀开源库中会经常用到. 还是以Glide为例, 其图片的磁盘缓存就使用了策略模式, 并提供了很多策略供用户选择:

    优秀源码总是设计巧妙但又易懂不晦涩.

    活动搞起了, 小光热干面欢迎大家常来光临啊, 喜欢您就收藏, 喜欢您就关注...

    相关文章

      网友评论

        本文标题:回馈顾客, 活动搞起---策略模式

        本文链接:https://www.haomeiwen.com/subject/nteppttx.html