zoukankan      html  css  js  c++  java
  • 第二章 策略模式 (Strategy)

    问题引入

    假设说,现在我们需要给超市的收银员写一款计算器,能让他们计算出顾客所购买商品的总价格,其中每种商品有不同的价格和数量。需求只有这些的话,这个计算器比较容易写:

    public class Cashier {
    
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
    
            double totalPrice = 0, price = 0, number = 0;
    
            do {
                System.out.print("请输入商品单价:");
                price = scanner.nextDouble();
    
                System.out.print("请输入商品数目:");
                number = scanner.nextInt();
    
                totalPrice += price * number;
    
                System.out.print("是否结束(y/n):");
                scanner.nextLine();
    
            } while ("n".equals(scanner.nextLine()));
    
            System.out.print("总价格为:" + totalPrice);
        }
    }
    

    运行结果如下:

    请输入商品单价:1
    请输入商品数目:2
    是否结束(y/n):n
    请输入商品单价:4
    请输入商品数目:6
    是否结束(y/n):y
    总价格为:26.0
    Process finished with exit code 0
    

    Cashier就简单的实现了收银员的功能。如果此时想加入打折的功能,比如说商品打9折,那么只需要再增加一个参数就可以了:

    public class Cashier2 {
    
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
    
            double totalPrice = 0, price = 0, number = 0, discount = 0;
    
            do {
                System.out.print("请输入商品单价:");
                price = scanner.nextDouble();
    
                System.out.print("请输入商品数目:");
                number = scanner.nextInt();
    
                System.out.print("请输入打折(小数):");
                discount = scanner.nextDouble();
                discount = discount <= 0 ? 1 : discount;
    
                totalPrice += price * number * discount;
    
                System.out.print("是否结束(y/n):");
                scanner.nextLine();
    
            } while ("n".equals(scanner.nextLine()));
    
            System.out.print("总价格为:" + totalPrice);
        }
    }
    

    运行结果如下:

    请输入商品单价:1
    请输入商品数目:2
    请输入打折(小数):0.5
    是否结束(y/n):n
    请输入商品单价:2
    请输入商品数目:5
    请输入打折(小数):0.5
    是否结束(y/n):y
    总价格为:6.0
    Process finished with exit code 0
    

    但是如果有其他的优惠呢,比如像是满200减50,满500打8折。这样每种的优惠都有不同的计算方式,或者称作是算法,就不能每次有新的优惠就去改Cashier的源码。可以把算法抽象出一个接口,具体的每种算法就是各个实现类。利用策略模式,代码如下:

    策略接口:

    public interface Strategy {
        /**
         * 计算优惠之后的价格
         *
         * @param money
         * @return
         */
        double figureUp(double money);
    }
    

    正常不打折,打折,满减的实现类如下:

    public class NormalStrategy implements Strategy {
    
        public double figureUp(double money) {
            return money;
        }
    }
    
    public class RebateStrategy implements Strategy {
    
        private double percent;
    
        public RebateStrategy(double percent) {
            this.percent = percent;
        }
    
        public double figureUp(double money) {
            return money * percent;
        }
    }
    
    public class ReturnStrategy implements Strategy {
    
        private double conditionNumber;
        private double discountNumber;
    
        public ReturnStrategy(double conditionNumber, double discountNumber) {
            this.conditionNumber = conditionNumber;
            this.discountNumber = discountNumber;
        }
    
        public double figureUp(double money) {
    
            if (money < conditionNumber) {
                return money;
            }
    
            double number = Math.floor(money / conditionNumber);
    
            return money - number * discountNumber;
        }
    }
    

    策略上下文:

    public class StrategyContext {
    
        private Strategy strategy;
    
        public StrategyContext(Strategy strategy){
            this.strategy = strategy;
        }
    
        public double compute(double money){
            return strategy.figureUp(money);
        }
    }
    

    测试类:

    public class Cashier3 {
    
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
    
            double totalPrice = 0, price = 0, number = 0;
            int discount = 0;
    
            StrategyContext strategyContext = null;
    
            do {
                System.out.print("请输入商品单价:");
                price = scanner.nextDouble();
    
                System.out.print("请输入商品数目:");
                number = scanner.nextInt();
    
                System.out.print("请输入优惠策略(1:正常,2:打9折,3:满200减50):");
                discount = scanner.nextInt();
    
                switch (discount) {
                    case 1:
                        strategyContext = new StrategyContext(new NormalStrategy());
                        price = strategyContext.compute(price);
                        break;
                    case 2:
                        strategyContext = new StrategyContext(new RebateStrategy(0.9));
                        price = strategyContext.compute(price);
                        break;
                    case 3:
                        strategyContext = new StrategyContext(new ReturnStrategy(200, 50));
                        price = strategyContext.compute(price);
                        break;
                }
    
                totalPrice += price * number;
    
                System.out.print("是否结束(y/n):");
                scanner.nextLine();
    
            } while ("n".equals(scanner.nextLine()));
    
            System.out.print("总价格为:" + totalPrice);
        }
    }
    

    运行结果:

    请输入商品单价:260
    请输入商品数目:1
    请输入优惠策略(1:正常,2:打9折,3:满200减50):3
    是否结束(y/n):n
    请输入商品单价:100
    请输入商品数目:1
    请输入优惠策略(1:正常,2:打9折,3:满200减50):2
    是否结束(y/n):y
    总价格为:300.0
    Process finished with exit code 0
    

    这样就将Cashier和具体的算法策略进行了解耦。不同的打折或者满减只需要传入不同的参数就可以了。相同类型的优惠抽象成了算法策略类,并且他们都继承自算法策略接口。但是还有点不足的是,还是需要在Cashier类中进行条件判断,如果增加新的算法策略还是需要修改Cashier类。可以通过融合简单工厂模式来改造,让Cashier只需要依赖StrategyContext类即可,有新的算法加入,对于Cashier来说也是无感知的。代码如下:

    修改StrategyContext类:

    public class StrategyContext2 {
    
        private Strategy strategy;
    
        public StrategyContext2(int strategyType){
            switch (strategyType) {
                case 1:
                    strategy = new NormalStrategy();
                    break;
                case 2:
                    strategy = new RebateStrategy(0.9);
                    break;
                case 3:
                    strategy = new ReturnStrategy(200, 50);
                    break;
            }
        }
    
        public double compute(double money){
            return strategy.figureUp(money);
        }
    }
    

    Cashier类修改:

    public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
    
            double totalPrice = 0, price = 0, number = 0;
            int discount = 0;
    
            StrategyContext2 strategyContext2 = null;
    
            do {
                System.out.print("请输入商品单价:");
                price = scanner.nextDouble();
    
                System.out.print("请输入商品数目:");
                number = scanner.nextInt();
    
                System.out.print("请输入优惠策略(1:正常,2:打9折,3:满200减50):");
                discount = scanner.nextInt();
    
                strategyContext2 = new StrategyContext2(discount);
                price = strategyContext2.compute(price);
    
                totalPrice += price * number;
    
                System.out.print("是否结束(y/n):");
                scanner.nextLine();
    
            } while ("n".equals(scanner.nextLine()));
    
            System.out.print("总价格为:" + totalPrice);
        }
    

    这样Cashier类只依赖于StrategyContext类,而无需依赖于具体的策略类。如果有新的优惠策略,对于调用者也就是Cashier来说是无感知的,它只需要传入新的参数即可。

    小结

    策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

    策略模式的主要优点如下。

    1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
      策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
      策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
    2. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
    3. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

    其主要缺点如下。

    1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
    2. 策略模式造成很多的策略类。
  • 相关阅读:
    java.lang.ClassCastException: java.util.HashMap$Values cannot be cast to java.util.List 转换异常
    React Swiper轮播图(二)
    超详细带你入门开发一个超实用的浏览器插件
    手臂太细如何增加纬度?这5个手臂锻炼动作,让你的手臂变粗壮
    2021百度世界大会精华总结(AI应用向)
    1、saltstack 安装部署
    MySQL的varchar(10)能存多少个汉字
    学习资料总结
    基于Spark的数据工厂(Data Factory):从设计到实现
    IntelliJ IDEA创建maven web项目(IDEA新手适用)
  • 原文地址:https://www.cnblogs.com/liuxiany/p/12639234.html
Copyright © 2011-2022 走看看