【读书笔记】大话设计模式 第二章 策略模式

前言

本篇实现一个模拟收银的小程序, 来了解什么是策略模式, 和策略模式的好处都有哪些, 另外, 不像上一篇博文, 一点一点衍化了, 省略特别简单的版本吧, 直接过渡到有一点封装的版本, 因为能看设计模式的应该都不是刚入行的新手, 有些东西一句话带过就可以了.

正文

场景:

一个简单的收银系统

界面如图


界面

输入价格单价, 根据所选的不同计费方式计算总价的功能, 界面就是为了直观化功能, 其实你在练习时完全可以用控制台模拟输入, 代替此界面, java画这样的界面很不友好.


代码

假设计费方式分为 "正常", "打8折", "满300-100", 分别用0, 1, 2表示, 可以写出如下方法:

// 单价, 数量, 计费方式
public static String operating(double price, double number, int type) {
    double total = 0;
    switch (type) {
    case 0:
        total = price * number;
        break;
    case 1:
        total = price * number * 0.8;
        break;
    case 2:
        total = price - Math.floor((price/300)) * 100;
        break;  
    default:
        break;
    }
    return String.valueOf(total);
}

尽量不要把分支留给客户端处理, 学习了简单工厂模式, 我们可以改造一下, 先像简单工厂模式过度.

首先明确工厂基类, 三者存在共性, 就是都处理同一个价格, 即商品的原价格. 然后分析这几中收费方式, 打折收费, 不管打8折, 打7折, 打几折应该都是打折的算法吧, 所以打折的类算一个实例, 正常的收费算一个实例, 满减活动算一个实例, 这里, 不可能会有人把打1~10折做10个类的对吧?

面向对象的编程, 并不是类越多越好, 类的划分是为了封装, 但分类的基础是抽象, 具有相同属性和功能的对象的抽象集合才是类

父类和子类:

// 处理价格超类
public abstract class CashPaySuper {
    // 处理价格 参数:原价格
    public abstract double calCash(double price);
}
// 正常收费类
public class CashPayNormal extends CashPaySuper {
    @Override
    public double calCash(double price) {
        return price;
    }
}
// 打折收费类
public class CashPayDiscount extends CashPaySuper {
    // 折扣率
    private double bate = 1;
    
    // 初始化时可传入折扣率
    public CashPayDiscount(double bate) {
        this.bate = bate;
    }
    
    @Override
    public double calCash(double price) {
        return price * bate;
    }
}
// 满减收费类
public class CashPayFullSub extends CashPaySuper {
    // 满多少
    double fullMoney = 0;
    // 减多少
    double subMoney = 0;
    
    public CashPayFullSub(double fullMoney, double subMoney) {
        this.fullMoney = fullMoney;
        this.subMoney = subMoney;
    }
    
    @Override
    public double calCash(double price) {
        
        if (price >= fullMoney) {
            price = price - Math.floor((price/fullMoney)) * subMoney;
        }
        return price;
    }
}

简单工厂类:

public class CashPayFactrory {

    public CashPaySuper createCashPay(int strategy) {
        CashPaySuper cs = null;
        switch (strategy) {
        case 0:
            //  正常销售
            cs = new CashPayNormal();
            return cs;
        case 1:
            //  打折销售(传入折扣率)
            cs = new CashPayDiscount(0.8);
            return cs;
        case 2:
            //  满减销售(传入满多少,减多少)
            cs = new CashPayFullSub(300, 100);
            return cs;
        default:
            break;
        }
        return null;
    }
}

客户端的执行代码:

public static String operating(double price, double number, int selectIndex) {
    CashPayFactrory cpf = new CashPayFactrory();
    CashPaySuper cs = cpf.createCashPay(selectIndex);
    return String.valueOf(cs.calCash(price * number));
}

简单工厂模式虽然解决了一定的问题,但这个模式只是解决对象的创建问题, 而且由于工厂本身包括了所有的收费方式, 商场可能要经常性的变更打折额度等,每次维护或扩展收费方式都要改动这个工厂, 以至代码需要重新编译部署, 所以它不是最好的办法, 面对算法的时常变动, 应该有更好的办法.

简单工厂模式

策略模式

不管商场如何促销,其实都是一些算法.用工厂来生成算法对象没有错, 但算法本身只是一种策略, 最重要的是这些算法随时都可能互相替换, 这就是变化点, 而封装变化点是我们面向对象的一种很重要的思维方式.

策略模式的基本结构图

策略模式

我将上述例子重写设计一下:
UML图部分名字我改了, 用来和上面的策略模式学习, 不用在意


收银系统策略模式

CashPaySuper就是抽象策略类, CashPayNormal, CashPayFullSub, CashPayDiscount类就是具体的策略, 所以这四个类不用改动.
只要加一个CashContext类, 再把客户端的执行代码稍微改动一下

CashContext类:

public class CashContext {

    CashPaySuper cashPay;
    
    public CashContext(CashPaySuper cashPay) {
        this.cashPay = cashPay;
    }

    public double Cal(double price) {
        return cashPay.calCash(price);
    }
    
}

执行代码:

public static String operating(double price, double number, int type) {
    CashContext context = null;
    switch (type) {
    case 0:
        context = new CashContext(new CashPayNormal());
        break;
    case 1:
        context = new CashContext(new CashPayDiscount(0.8));
        break;
    case 2:
        context = new CashContext(new CashPayFullSub(300, 100));
        break;  
    default:
        break;
    }
    return String.valueOf(context.Cal(price*number));
}

执行代码和之前的比, 好像又回到了第一版的样子, 又在客户端处理分支了, 所以我们需要把分支想办法挪走.

策略模式与简单工厂模式结合

改造CashContext类:

public class CashContext {

    CashPaySuper cashPay = null;
    
    public CashContext(int type) {
        switch (type) {
        case 0:
            this.cashPay = new CashPayNormal();
            break;
        case 1:
            this.cashPay = new CashPayDiscount(0.8);
            break;
        case 2:
            this.cashPay = new CashPayFullSub(300, 100);
            break;  
        default:
            break;
        }
    }
    
    public double Cal(double price) {
        return cashPay.calCash(price);
    }
    
}

最后改造执行代码:

public static String operating(double price, double number, int type) {
    return String.valueOf(new CashContext(type).Cal(price*number));
}

对比之前的简单工厂的代码:

public static String operating(double price, double number, int selectIndex) {
    CashPayFactrory cpf = new CashPayFactrory();
    CashPaySuper cs = cpf.createCashPay(selectIndex);
    return String.valueOf(cs.calCash(price * number));
}

显然代码更精简了, 暴露给客户端的东西更少了, 仅一个CashContext对象而已, 耦合度降低了.

策略模式解析

策略模式是一种定义一系列算法的方法, 从概念上来看, 所有这些算法的完成都是相同的工作, 只是实现不同, 它可以以相同的方式调用所有的算法, 减少了各种算法类与使用算法之间的耦合.

关于策略模式的总结

  • 策略模式简化了单元测试, 每个算法都有自己的类, 可以通过自己的接口单独测试
  • 修改其中一个算法, 不会影响其他算法
  • 当不同的行为堆砌到一个类中, 难免有switch , if等分支, 将这些行为封装在一个个独立的Strategy类中, 可以在使用这些行为的类中消除条件语句
  • 策略模式几乎可以封装任何类型的规则, 只要在分析过程中听到需要在不同的时间应用不同的业务规则, 就可以考虑使用策略模式处理这种变化的可能性
  • 基本的策略模式, 选择所用具体实现的职责由客户端承担, 并转给策略模式的Context对象.但与工厂模式结合后, 选择的职责也由Context承担, 大大减轻了客户端的职责.

参考书籍

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

推荐阅读更多精彩内容

  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,652评论 3 13
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,811评论 1 15
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • 1 场景问题# 1.1 报价管理## 向客户报价,对于销售部门的人来讲,这是一个非常重大、非常复杂的问题,对不同的...
    七寸知架构阅读 4,869评论 9 60
  • 三叔的病 我总是想说些什么的,可是转念一想却又没什么可说。就像我有一肚子笑话想讲给别人...
    314a0b88edff阅读 185评论 0 0