大话设计模式笔记(二)の策略模式

举个栗子

问题描述

商场收银软件,营业员根据客户所购买的商品单价和数量,向客户收费。

简单实现

/**
 * 普通实现
 * Created by callmeDevil on 2019/6/1.
 */
public class NormalTest {

    public static void main(String[] args) {
        double price = 10;
        double num = 5;
        System.out.println(String.format("单价:%s 元", price));
        System.out.println(String.format("数量:%s 个", num));
        System.out.println(String.format("总价:%s 元", calculateTotal(price, num)));
    }

    /**
     * 计算总价
     *
     * @param price 单价
     * @param num   数量
     * @return
     */
    private static double calculateTotal(double price, double num) {
        return price * num;
    }

}

问题2

商品搞促销,打八折,也可能打七折,甚至五折。

数组实现

/**
 * 普通实现2
 * Created by callmeDevil on 2019/6/1.
 */
public class NormalTest2 {

    public static void main(String[] args) {
        double price = 10;
        double num = 5;
        String[] discounts = {"正常收费", "打八折", "打七折", "打五折"};
        System.out.println(String.format("单价:%s 元", price));
        System.out.println(String.format("数量:%s 个", num));
        System.out.println(String.format("折扣:%s ", discounts[1]));
        System.out.println(String.format("总价:%s 元", calculateTotal(price, num, 1)));
    }

    /**
     * 计算总价
     *
     * @param price    单价
     * @param num      数量
     * @param discount 折扣
     * @return
     */
    private static double calculateTotal(double price, double num, int discount) {
        double total = 0L;
        switch (discount) {
            case 0:
                total = price * num;
                break;
            case 1:
                total = price * num * 0.8;
                break;
            case 2:
                total = price * num * 0.7;
                break;
            case 3:
                total = price * num * 0.5;
                break;
            default:
                break;
        }
        return total;
    }

}

上述方式存在问题

有很多重复代码,就switch语句来说,如果计算方式比较复杂,那么这里就会显得非常冗余,必须考虑重构,抽出共性代码。而且如果需要打其他折扣,修改的地方也很多。

使用简单工厂模式

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

/**
 * 现金收费抽象类
 * Created by callmeDevil on 2019/6/1.
 */
public abstract class CashSuper {
    /**
     * 收取现金
     *
     * @param money 原价
     * @return 当前价
     */
    public abstract double acceptCash(double money);
}
/**
 * 正常收费子类
 * Created by callmeDevil on 2019/6/1.
 */
public class CashNormal extends CashSuper {
    @Override
    public double acceptCash(double money) {
        // 正常收费,原价返回
        return money;
    }
}

/**
 * 返利收费子类
 * Created by callmeDevil on 2019/6/1.
 */
public class CashReturn extends CashSuper{

    // 返利条件
    private double moneyCondition = 0;
    // 返利值
    private double moneyReturn = 0;

    // 返利收费,初始化时必须输入返利条件和返利值,比如满300返100,
    // 则moneyCondition 为300,moneyReturn 为100
    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double money) {
        double result = money;
        if (money >= moneyCondition) {
            // 若大于返利条件,则需要减去返利值
            result = money - Math.floor(money / moneyCondition) * moneyReturn;
        }
        return result;
    }

}
/**
 * 打折收费子类
 * Created by callmeDevil on 2019/6/1.
 */
public class CashRebate extends CashSuper{

    // 折扣率
    private double moneyRebate = 1;

    public CashRebate(double moneyRebate) {
        // 打折收费,初始化时,必须输入折扣率,如打八折,就是0.8
        this.moneyRebate = moneyRebate;
    }

    @Override
    public double acceptCash(double money) {
        return money * moneyRebate;
    }

}
/**
 * 现金收费工厂类
 * Created by callmeDevil on 2019/6/1.
 */
public class CashFactory {

    /**
     * 创建现金收取工厂实例
     *
     * @param type 收费类型
     * @return
     */
    public static CashSuper createCashAccept(String type) {
        CashSuper cs = null;
        switch (type) {
            case "正常收费":
                cs = new CashNormal();
                break;
            case "满300减100":
                cs = new CashReturn(300, 100);
                break;
            case "打8折":
                cs = new CashRebate(0.8);
                break;
            default:
                break;
        }
        return cs;
    }

}
/**
 * 现金收费测试
 * Created by callmeDevil on 2019/6/1.
 */
public class CashTest {

    public static void main(String[] args) {
        double price = 400;
        double num = 3;
        System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));

        String type = "正常收费";
        CashSuper cashSuper = CashFactory.createCashAccept(type);
        double total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));

        type = "满300减100";
        cashSuper = CashFactory.createCashAccept(type);
        total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));

        type = "打8折";
        cashSuper = CashFactory.createCashAccept(type);
        total = cashSuper.acceptCash(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
    }

}

输出结果

单价:400.0 元,数量:3.0 个
折扣:正常收费;总价:1200.0 元
折扣:满300减100;总价:900.0 元
折扣:打8折;总价:960.0 元

仍然存在的缺点

简单工厂模式虽然也能够解决问题2,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费模式,商场是可能经常性的更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是很糟糕的,所以不是最好的解决办法。

策略模式

概念

定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不回影响到使用算法的客户。

UML图

代码实现

其实上面的简单工厂模式实现方式里面的CashSuper、CashNormal、CashRebate、CashReturn都不需要更改,只需要增加一个CashContext类,同时修改下客户端就可以了。

/**
 * 现金上下文
 * Created by callmeDevil on 2019/6/1.
 */
public class CashContext {

    private CashSuper cs = null;

    public CashContext(String type) {
        switch (type) {
            case "正常收费":
                cs = new CashNormal();
                break;
            case "满300减100":
                cs = new CashReturn(300, 100);
                break;
            case "打8折":
                cs = new CashRebate(0.8);
                break;
            default:
                break;
        }
    }

    public double getResult(double money) {
        return cs.acceptCash(money);
    }

}
/**
 * 策略模式测试
 * Created by callmeDevil on 2019/6/1.
 */
public class ContextTest {

    public static void main(String[] args) {
        double price = 400;
        double num = 3;
        System.out.println(String.format("单价:%s 元,数量:%s 个", price, num));

        String type = "正常收费";
        CashContext cashContext = new CashContext(type);
        double total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));

        type = "满300减100";
        cashContext = new CashContext(type);
        total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));

        type = "打8折";
        cashContext = new CashContext(type);
        total = cashContext.getResult(price) * num;
        System.out.println(String.format("折扣:%s;总价:%s 元", type, total));
    }

}

需要注意的是

策略模式测试类中的代码与简单工厂的非常相似,因为这里将策略模式与简单工厂模式做了结合,因此比较难以判断策略模式的好处到底在哪。如果仔细分析一下会发现,只用简单工厂的测试类中,也就是客户端代码耦合了CashSuperCashFactory两个类,而使用了策略模式的客户端只涉及到了CashContext一个类,将客户端与具体算法的实现进行了解耦,这样如果商场需要变更促销折扣时,除了变更具体的折扣实现类,只需要更改CashContext即可,客户端完全不用做任何更改,这就是策略模式带来的最大好处。

总结

  • 策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
  • 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。
  • 策略模式另一个优点就是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  • 当不同的行为堆砌在一个类中,就很难避免使用条件语句来选择合适的行为,将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
  • 策略模式封装了算法,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
  • 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。
  • 最后不得不说的是,每增加一种算法,都免不了修改CashContext中的switch分支,这是没办法的,因为任何需求的变更都需要成本
(0)

相关推荐

  • 方法重载

    方法名必须相同,参数列表不同(类型或个数或排列顺序不同) public static int max(int num1, int num2) { //方法体 } public static doubl ...

  • 点外卖,让我想起了 策略模式【原创】

    回复"000"获取大量电子书 本篇文章是设计模式系列的第三篇: 模板模式 单例模式 今天给大家分享的是策略模式,具体内容大纲如下: 生活案例 在这互联网时代,尤其是在城市中,有一帮 ...

  • python---策略模式

    目录 python–策略模式 前言 一. 应用 二. 避免过多使用if-else 三. 使用策略,工厂模式. python–策略模式 前言 策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的 ...

  • 设计模式 | 工厂方法模式(factory method)

    定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 结构:(书中图,侵删) 一个工厂的抽象接口 若干个具体的工厂类 一个需要创建对象的抽象接口 若干个 ...

  • 策略模式

    有道无术,术可求 有术无道,止于术 一.策略模式的定义 先举一个例子来说:在网上购物的时候,有的时候会有一些打折的活动,可能会给你5元的优惠券,也有可能会给一张满减的优惠券,而无论给怎样的优惠券,到了 ...

  • 大话设计模式笔记(十七)の迭代器模式

    迭代器模式 定义 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示. 什么时候用? 当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式. ...

  • 大话设计模式笔记(十三)の状态模式

    举个栗子 问题描述 上班的日子,上午状态好,中午想睡觉,下午渐恢复,加班苦煎熬.根据时间的不同体现不同的工作状态. 简单实现 Work /** * 工作类 * Created by callmeDev ...

  • 大话设计模式笔记(七)の原型模式

    举个栗子 问题描述 要求有一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历. 简单实现 简历类 /** * 简历类 * Created by callmeDevil o ...

  • 大话设计模式笔记(六)の工厂方法模式

    栗子回顾 简单工厂模式: https://www.cnblogs.com/call-me-devil/p/10926633.html 运算类使用工厂方法模式实现 UML图 代码实现 工厂接口 /** ...

  • 大话设计模式笔记(五)の代理模式

    举个栗子 故事是这样的... 一个小伙子喜欢上了隔壁班的一个妹子,但是又不认识,也害羞不好意思主动去说话,于是拜托了同样在这个班的一个朋友去传递自己想要送的礼物... 代码实现 该模式就不上什么简单实 ...

  • 大话设计模式笔记(一)の简单工厂模式

    概要 一个好的程序猿/媛敲出来的代码应该是可维护.可复用.可扩展的,也就是具有较好的灵活性. 为了达到以上目的,在还没敲代码之前,需要事先考虑通过何种方式能够使自己的程序的耦合度降低,最基本的便是面向 ...

  • 深入学习《大话设计模式》 简单工厂模式

    简单工厂模式 定义:封装改变,既然要封装改变,自然也就要找到需要更改的代码,然后将需要更改的代码用类来封装,这样的思路就是我们简单工厂模式的实现方式了 下面我们通过一则小故事来简述一下我们在项目中为什 ...

  • 大话设计模式笔记(十八)の单例模式

    单例模式 定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象.一个最好的办法就是,让类自身负责保存它的唯一实例.这 ...

  • 大话设计模式笔记(四)の装饰模式

    举个栗子 问题描述 可以给人搭配嘻哈服或白领装的程序. 简单实现 代码 /** * 人类 * Created by callmeDevil on 2019/6/23. */ public class ...