第二章 策略模式 (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. 策略模式造成很多的策略类。

posted on 2020-04-05 21:36  liuxiany  阅读(173)  评论(0编辑  收藏  举报

导航