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


    举个栗子

    问题描述

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

    简单实现

    /**
     * 普通实现
     * 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分支,这是没办法的,因为任何需求的变更都需要成本
  • 相关阅读:
    Codeforces Round #614 (Div. 2) D. Aroma's Search
    Codeforces Round #614 (Div. 2) C. NEKO's Maze Game
    Kruskal最小生成树及应用
    Codeforces Round #608 (Div. 2) E. Common Number
    Codeforces Round #607 (Div. 2) D Beingawesomeism
    codeforce Hello 2020 A~E
    Codeforces Round #609 (Div. 2)
    Codeforces Round #607 (Div. 2) C. Cut and Paste
    Codeforces Round #605 (Div. 3) F. Two Bracket Sequences 三维dp
    2019-2020Nowcoder Girl初赛题解
  • 原文地址:https://www.cnblogs.com/call-me-devil/p/10959205.html
Copyright © 2020-2023  润新知