• 大话设计模式读书笔记(策略模式)


    人物:小菜,大鸟

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


    策略模式:

    1.大鸟让小菜做一个小软件,能输入单价和数量,计算总价,小菜第一次很粗糙地完成了

    2.大鸟指出了小菜软件的不可扩展性,不灵活等缺点,然后小菜回想着上次学习的简单工厂模式,接着进行了小菜的第二次实现

    3.大鸟又指出频繁的策略变动可能会导致重复的代码上线,让小菜继续改进,于是在大鸟的引导下,开始先了解起了策略模式

    4.在了解了策模式后,小菜将策略模式融入软件,又发现又得在客户端进行判断(这个缺点具体参考简单工厂模式一章)

    5.小菜最后将简单工厂模式和策略模式相结合,完成了设计与实现

    6.小结了策略模式

    小菜初次尝试 

    用两个文本框输入单价和数量,一个确定按钮算出每种商品费用,列表框记录商品清单,一个标签记录总计,最后加上一个重置按钮,关键代码如下:

    @Slf4j
    public class CashierSystem {
        BigDecimal totalPrice = BigDecimal.ZERO;
    
        private BigDecimal getTotalPrice(BigDecimal price, BigDecimal num) {
            totalPrice = totalPrice.add(price.multiply(num)).setScale(2, RoundingMode.HALF_UP);
            return totalPrice;
        }
    
        public static void main(String[] args) {
            CashierSystem result = new CashierSystem();
    
            BigDecimal applePrice = new BigDecimal("2");
            BigDecimal appleNumber = new BigDecimal("5");
            result.getTotalPrice(applePrice, appleNumber).toString();
    
            BigDecimal peachPrice = new BigDecimal("6.6");
            BigDecimal peachNumber = new BigDecimal("6");
            String resultPrice = result.getTotalPrice(peachPrice, peachNumber).toString();
    
            log.info("总价为:{}", resultPrice);
        }
    }

    大鸟:那现在商场要求对商品搞活动,所有商品打八折

    小菜:那在最后的totalPrice乘以0.8不就行了?

    大鸟:那这样不是活动完了,还要把代码再改一遍?

    小菜:那我增加一个下拉框,可以选择是打8折还是原价

    小菜尝试将可能打折的内容全部列出:

    public enum DiscountTypeEnum {
    
        NO_DISCOUNT(BigDecimal.ONE, "原价"),
    
        DISCOUNT_EIGHTY(new BigDecimal("0.8"), "打八折"),
    
        DISCOUNT_HALF(new BigDecimal("0.5"), "打半折");
    
        private BigDecimal code;
        private String message;
    
        DiscountTypeEnum(BigDecimal code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public BigDecimal getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }

    然后前端页面只要选了相应的折扣,就能直接在totalPrice上做打折处理:

    @Slf4j
    public class CashierSystem {
        BigDecimal totalPrice = BigDecimal.ZERO;
    
        private BigDecimal getTotalPrice(BigDecimal price, BigDecimal num) {
            totalPrice = totalPrice.add(price.multiply(num)).setScale(2, RoundingMode.HALF_UP);
            return totalPrice;
        }
    
        public static void main(String[] args) {
            CashierSystem result = new CashierSystem();
            BigDecimal peachPrice = new BigDecimal("6.6");
            BigDecimal peachNumber = new BigDecimal("6");
            String resultPrice = result.getTotalPrice(peachPrice, peachNumber)
                    .multiply(DiscountTypeEnum.DISCOUNT_EIGHTY.getCode())
                    .toString();
            log.info("总价为:{}", resultPrice);
        }
    }

    大鸟:但是你列出的打折可能性有限,如果又出现满300返100,满700返300的活动,那就不只是做乘法了,肯定还会改变原来代码逻辑的,那又该怎么办?

    小菜:那用简单工厂模式,根据需求,子类有几个写几个,如:打八折,打半折,满300返100等,都写上

    大鸟:用设计模式的时候先想想,怎么用合理,难道后面打三折还要再加一个子类?要知道哪些是同一类型的,哪些是不同的

    小菜用简单工厂模式的尝试:

    先划分子类的类型,现在可以区分为三种,一种是正常售卖,一种是打折类型,直接初始化参数即可,最后一种是满减,用两个参数做传参即可:

    现金收费抽象类:

    public abstract class AbstractCashierSystem {
        public abstract BigDecimal acceptCash(BigDecimal money);
    }

    原价收费子类:

    public class CashNormal extends AbstractCashierSystem {
        @Override
        public BigDecimal acceptCash(BigDecimal money) {
            return money;
        }
    }

    打折收费子类:

    @Data
    public class CashRebate extends AbstractCashierSystem {
        private String discountType;
    
        @Override
        public BigDecimal acceptCash(BigDecimal money) {
            //从客户端传来的discountType,这里举例:"NO_DISCOUNT"
            String discountType = "NO_DISCOUNT";
            return money.multiply(DiscountTypeEnum.valueOf(discountType).getCode());
        }
    
        CashRebate(String discountType) {
            this.discountType = discountType;
        }
    }

    返利收费子类:

    @Data
    public class CashReturn extends AbstractCashierSystem {
        private BigDecimal moneyCondition;
        private BigDecimal moneyReturn;
    
        @Override
        public BigDecimal acceptCash(BigDecimal money) {
            BigDecimal result = money;
            if (money.compareTo(moneyCondition) > 0) {
                result = money.subtract(
                        money.divide(moneyCondition, 4, RoundingMode.HALF_UP).multiply(moneyReturn)
                );
            }
            return result;
        }
    
        public CashReturn(BigDecimal moneyCondition, BigDecimal moneyReturn) {
            this.moneyCondition = moneyCondition;
            this.moneyReturn = moneyReturn;
        }
    
    }

    收费工厂类:

    public class CashFactory {
        public static AbstractCashierSystem createCashAccept(String type) {
            AbstractCashierSystem cs = null;
            switch (type) {
                case "正常收费":
                    cs = new CashNormal();
                    break;
                case "满300返100":
                    CashReturn cr1 = new CashReturn(new BigDecimal("300"), new BigDecimal("100"));
                    cs = cr1;
                    break;
                case "打8折":
                    CashRebate cr2 = new CashRebate(DiscountTypeEnum.DISCOUNT_EIGHTY.getMessage());
                    cs = cr2;
                    break;
            }
            return cs;
        }
    }

    客户端:

    @Slf4j
    public class CashOperation {
        public static void main(String[] args) {
            BigDecimal totalPrice;
            //比如客户端选择的是满返,满300返100
            AbstractCashierSystem cs = CashFactory.createCashAccept("满300返100");
            totalPrice = cs.acceptCash(new BigDecimal("400"));
            log.info("合计总价为:{}", totalPrice);
        }
    }

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

    小菜:这样设计好后

      1.如果是再加满500返200,则客户端加一个选项即可

      2.如果再出新的促销方式,如满100积分10分,则再出一个子类分支即可,不会影响之前代码

    大鸟:不错,简单工厂模式看似已经解决了这个问题,但是只是解决了对象的创建,从实际情况出发,工厂本身就包含了多种收费模式,可能经常性地更改打折额度和返回额度,那么每次都要重新改代码重新部署,那还有没有更好的方法来解决呢?

    试用策略模式

    定义:策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变换,不会影响到试用算法的用户

    什么是算法:从上面的例子来看,打折和返利都是一种算法

    为什么用策略模式:用工厂生成算法对象,这没有错,但算法本身是一种策略,最重要的是算法是随时可以相互替换的,这就是变化点,而封装变化点是面向对象的一种很重要的方式

    策略模式简要实现:

    抽象算法类(定义支持的所有算法的公共接口):

    public abstract class Strategy {
        public abstract void AlgorithmInterface();
    }

    具体算法A:

    public class ConcreteStrategyA extends Strategy {
        @Override
        public void AlgorithmInterface() {
        }
    }

    具体算法B:

    public class ConcreteStrategyB extends Strategy {
        @Override
        public void AlgorithmInterface() {
        }
    }

    Context:用于对Strategy对象的引用

    public class Context {
        Strategy strategy;
    
        /**
         * 初始化时传入的策略
         * @param strategy
         */
        public Context(Strategy strategy) {
            this.strategy = strategy;
        }
    
        /**
         * 根据具体策略对象,调用其算法的方法
         */
        public void ContextInterface() {
            strategy.AlgorithmInterface();
        }
    }

    客户端代码:

    public class StrategyCashOperation {
        public static void main(String[] args) {
            Context context;
    
            context = new Context(new ConcreteStrategyA());
            context.ContextInterface();
    
            context = new Context(new ConcreteStrategyB());
            context.ContextInterface();
        }
    }

    小菜尝试将策略模式融入收银系统

    其实正常收费,返利,满减都是一种具体策略,AbstractCashierSystem是抽象策略,现在只要加入Context类,再改下客户端即可:

    添加CashContext类:

    public class CashContext {
        private AbstractCashierSystem cs;
    
        public CashContext(AbstractCashierSystem cs) {
            this.cs = cs;
        }
    
        public BigDecimal GetResult(BigDecimal money) {
            return cs.acceptCash(money);
        }
    }

    客户端代码调整:

    @Slf4j
    public class CashOperation {
        public static void main(String[] args) {
            BigDecimal totalPrice;
            CashContext cs = null;
            BigDecimal money;
            //客户端传入策略对象type
            String type = "正常收费";
            switch (type) {
                case "正常收费":
                    cs = new CashContext(new CashNormal());
                    break;
                case "满300返100":
                    cs = new CashContext(new CashReturn(new BigDecimal("300"), new BigDecimal("100")));
                    break;
                case "打8折":
                    cs = new CashContext(new CashRebate(DiscountTypeEnum.DISCOUNT_EIGHTY.getMessage()));
                    break;
            }
            //客户端传入金额500
            money = new BigDecimal("500");
            totalPrice = cs.GetResult(money);
            log.info("总额:{}", totalPrice);
        }
    }

    大鸟:策略模式用进去了,但是在客户端判断策略模式,不是又走了之前的老路么,怎么讲判断转移?试试策略模式和工厂模式的结合

    改造后的CashContext:

    public class CashContext {
        private AbstractCashierSystem cs;
    
        public CashContext(String type) {
            switch (type) {
                case "正常收费":
                    CashNormal cs0 = new CashNormal();
                    cs = cs0;
                    break;
                case "满300返100":
                    CashReturn cs1 = new CashReturn(new BigDecimal("300"), new BigDecimal("100"));
                    cs = cs1;
                    break;
                case "打8折":
                    CashRebate cs2 = new CashRebate(DiscountTypeEnum.DISCOUNT_EIGHTY.getMessage());
                    cs = cs2;
                    break;
            }
        }
    
        public BigDecimal GetResult(BigDecimal money) {
            return cs.acceptCash(money);
        }
    }

    客户端改造:

    @Slf4j
    public class CashOperation {
        public static void main(String[] args) {
            //客户端传入策略对象type
            String type = "正常收费";
            CashContext cs = new CashContext(type);
    
            //客户端传入金额500
            BigDecimal money = new BigDecimal("500");
    
            //通过对Context的GetResult方法的调用,可以得到收取费用的结果,让具体算法与客户进行了格隔离
            BigDecimal totalPrice = cs.GetResult(money);
            log.info("总额:{}", totalPrice);
        }
    }

    思考:原来简单工厂模式并非只有建立一个工厂类的做法,也可以这样做,那么简单工厂模式和上面两种模式的结果到底有什么不同呢?

    简单工厂模式:

    AbstractCashierSystem cs = CashFactory.createCashAccept("满300返100");

    两者结合:

    CashContext cs = new CashContext(type);

    答:可以看出,简单工厂模式要识别AbstractCashierSystem和CashFactory两个类,而两种模式结合后,只用识别CashContext一个类就行了

          这样的好处 --> 耦合度更低,我们在客户端实例化的是CashContect的对象,调用的是CashContext的getResult方法,这使得具体的收费算法与客户端彻底分离

    策略模式解析

    1.什么是策略模式:

      (1)策略模式是一种定义一系列算法的方法

      (2)从概念来看,所有算法完成工作相同只是实现不同,策略模式可以以相同的方式调用所有算法,减少了算法类与使用算法类之间的耦合

    2.有什么好处:

      (1)解耦,如上面所说

      (2)策略模式的Strategy类层次为Context定义了一系列可重用的算法或行为,继承有助于析取出这些算法的公共功能,比如这里的Strategy类是:AbstractCashierSystem,然后在Context里定义了getResult()的方法,这样所有继承了AbstractCashierSystem的子类,都可以用Context里的getResutl()方法

      (3)简化了单元测试,每个算法都有自己的类,可以通过自己的接口单独测试

     3.小结:

      策略模式封装了变化。它可以用来封装任何类型的规则,所以只要在分析过程中遇到需要不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

  • 相关阅读:
    ubuntu配置服务器环境
    discuz安装与学习资料
    前端面试题总结(一)
    css公共样式,初始化
    js的解析--预处理(三)
    sass的安装与基础
    移动开发学习笔记(一) 移动开发的注意事项
    移动前端一些常用的框架
    JavaScript的构造器与对象(二)
    JavaScript 中的Object的使用详解笔记(一)
  • 原文地址:https://www.cnblogs.com/wencheng9012/p/13365500.html
Copyright © 2020-2023  润新知