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

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为例, 其图片的磁盘缓存就使用了策略模式, 并提供了很多策略供用户选择:

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容