策略模式

177 阅读7分钟

策略模式

本篇策略模式,借鉴参考《大话设计模式》中的例子,使用了 Java 来进行简单的实现。

一个商场收银系统,营业员根据客户所购买商品的单价和数量,可能需要根据不同的促销活动计算不同的折扣,向客户收费。

mermaid 的类图在手机上显示不友好,推荐使用电脑进行阅读。也可以克隆项目到本地,在 idea 中安装 mermaid 插件进行阅读。

项目地址:design-patterns ,将会持续更新各种模式的应用,以及后续的实际案例应用。

普通实现

public class Cashier {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入商品单价:");
        String unitPrice = scanner.nextLine();
        System.out.print("请输入商品数量:");
        String quantity = scanner.nextLine();
        System.out.println("1. 9 折");
        System.out.println("2. 8 折");
        System.out.println("3. 7 折");
        System.out.println("4. 不打折");
        System.out.println("请选择折扣:");
        String discount = scanner.nextLine();

        double result;
        switch (discount) {
            case "1":
                result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.9;
                break;
            case "2":
                result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.8;
                break;
            case "3":
                result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity) * 0.7;
                break;
            case "4":
                result = Double.parseDouble(unitPrice) * Double.parseDouble(quantity);
                break;
            default:
                System.out.println("折扣选择错误");
                return;
        }

        System.out.printf("您购买了 %s 件商品,单价 %s,折扣方案:%s,总价格为:%s %n", quantity, unitPrice, discount, result);
    }
}

以上代码是一个简单的商场收银系统的实现,在控制台输入商品单价和购买的商品数量,并选择对应的折扣,就可以计算出商品的最终价格。

可以观察一下代码,switch 的各个分支语句其实只是执行了打几折的算法动作,除了折扣不同以外,并没有什么区别;如果现在又来个需求,商场推出了满减活动,满 300 减 20,这个怎么实现呢?继续添加条件判断已经不合适了,因为活动力度可能会随时变化,或者再推出送积分活动,满 100 送 10 积分,可以使用积分兑换商品等。

这里可能想到用简单工厂模式去实现,但是仔细思考一下,每次推出一个活动,九折、八折、七折、六折、满 300 减 20、满 100 减 5 ...... 都需要新增一个子类,这样就不太适合使用简单工厂实现了,如果对象类型较少,并且不太可能增加新的类型,那么使用简单工厂模式更加简单和方便。

改造策略模式

当需要运行根据不同的条件,选择不同的算法或行为时,可以使用策略模式。策略模式将算法或行为封装成独立的类,并且使它们可以互相替换。这样可以使算法或行为的变化独立于使用它们的客户端代码。

策略模式的核心是定义一个策略接口,该接口定义了算法或行为的共同方法。然后,在具体算法或行为的类中实现该接口,以便在运行时选择不同的算法或行为。

在此商场收银系统的案例中,可以总结一下相同点,打九折、打八折、打七折...... 等均是同一个行为或者说同一类算法,只不过打折的力度不同。

以下是策略模式的一般实现步骤:

  1. 定义一个策略接口(或抽象类),它定义了算法或行为的方法:
public interface Strategy {
    public void doSomething();
}
  1. 实现策略接口的具体策略类,每个具体策略类实现了策略接口中的方法。
public class ConcreteStrategyA implements Strategy {
    public void doSomething() {
        // 实现具体策略A的方法
    }
}

public class ConcreteStrategyB implements Strategy {
    public void doSomething() {
        // 实现具体策略B的方法
    }
}
  1. 定义一个上下文类,它持有策略接口的引用,可以在运行时动态地切换不同的策略。
public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.doSomething();
    }
}
  1. 在客户端代码中,创建具体的策略对象,并将它们注入到上下文类中,然后调用上下文类的方法来执行具体的策略。
public class Client {
    public static void main(String[] args) {
        Strategy strategyA = new ConcreteStrategyA();
        Strategy strategyB = new ConcreteStrategyB();

        Context context = new Context(strategyA);
        context.executeStrategy();  // 执行具体策略A的方法

        context.setStrategy(strategyB);
        context.executeStrategy();  // 执行具体策略B的方法
    }
}

收银系统策略模式实现

参考一般策略模式的步骤

  1. 定义一个计算支付金额的抽象类,并定义一个抽象方法:
public abstract class CashSuper {

    /**
     * 计算需支付金额
     *
     * @param money 钱
     * @return {@link Double} 折后价格
     */
    public abstract Double acceptCash(Double money);

}
  1. 实现具体的策略类(也就是各种折扣类)
/**
 * 正常收费算法
 */
public class CashNormal extends CashSuper {
    @Override
    public Double acceptCash(Double money) {
        return money;
    }
}


/**
 * 折扣算法
 */
public class CashRebate extends CashSuper {

    private final Double moneyRebate;

    /**
     * 折扣算法
     *
     * @param moneyRebate 折扣
     */
    public CashRebate(Double moneyRebate) {
        this.moneyRebate = moneyRebate;
    }

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


/**
 * 满减算法
 */
public class CashReturn extends CashSuper {

    private final Double moneyCondition;

    private final Double moneyReturn;

    /**
     * 满减算法
     *
     * @param moneyCondition 满减条件
     * @param moneyReturn    满减金额
     */
    public CashReturn(Double moneyCondition, Double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public Double acceptCash(Double money) {
        if (money >= moneyCondition) {
            return money - (money / moneyCondition) * moneyReturn;
        }
        return money;
    }
}
  1. 定义一个上下文类
public class CashContext {

    private CashSuper cashSuper;


    public CashContext(String type) {
        switch (type) {
            case "1":
                cashSuper = new CashRebate(0.9d);
                break;
            case "2":
                cashSuper = new CashRebate(0.8d);
                break;
            case "3":
                cashSuper = new CashRebate(0.7d);
                break;
            case "4":
                cashSuper = new CashNormal();
                break;
            case "5":
                cashSuper = new CashReturn(300d, 100d);
                break;
            default:
                System.out.println("折扣选择错误");
        }
    }

    public Double getResult(Double quantity, Double unitPrice) {
        return cashSuper.acceptCash(quantity * unitPrice);
    }
}

此处简单工厂模式和策略模式一起使用,将简单工厂模式用来创建不同的策略对象,然后将这些策略对象注入到策略模式的上下文中,这样就可以动态的选择不同的算法或行为。

  1. 客户端
public class Cashier {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入商品单价:");
        String unitPrice = scanner.nextLine();
        System.out.print("请输入商品数量:");
        String quantity = scanner.nextLine();
        System.out.println("1. 9 折");
        System.out.println("2. 8 折");
        System.out.println("3. 7 折");
        System.out.println("4. 不打折");
        System.out.println("5. 满 300 减 100");
        System.out.println("请选择折扣:");
        String discount = scanner.nextLine();

        CashContext cc = new CashContext(discount);
        double result = cc.getResult(Double.parseDouble(quantity), Double.parseDouble(unitPrice));

        System.out.printf("您购买了 %s 件商品,单价 %s,折扣方案:%s,总价格为:%s %n", quantity, unitPrice, discount, result);
    }
}

结构图

---
title: 商场收银系统结构图
---
classDiagram
    direction rl
    CashNormal --|> CashSuper
    CashRebate --|> CashSuper
    CashReturn --|> CashSuper

    CashContext o--> CashSuper
    class CashSuper {
        <<Abstract>>
        + acceptCash(Double money) Double
    }

    class CashContext {
        - cashSuper CashSuper
        + CashContext(String type)
        + getResult(Double quantity, Double unitPrice) Double
    }

    class CashNormal {
        + acceptCash(Double money) Double
    }

    class CashRebate {
        + CashRebate(Double moneyRebate)
        + acceptCash(Double money) Double
    }

    class CashReturn {
        - moneyCondition Double
        - moneyReturn Double
        + CashReturn(Double moneyCondition, Double moneyReturn)
        + acceptCash(Double money) Double
    }

优缺点

优点

  1. 策略模式能够封装算法,使得算法可以独立于客户端而变化,增加了程序的灵活性。

  2. 策略模式提供了一种解耦的方式,将算法与客户端代码分离开来,使得代码更加易于维护和扩展。

  3. 策略模式可以避免大量的条件判断语句,提高代码的可读性和可维护性。

缺点

  1. 策略模式增加了代码的复杂度,需要在客户端代码中指定使用哪种算法。

  2. 如果算法很多,就需要创建很多的策略类,增加了类的数量,可能会影响程序的性能。

适用场景

  1. 多种算法实现:如果一个问题有多种解决方案或者多种算法实现,可以使用策略模式将这些算法实现封装起来,使得客户端代码可以动态地选择不同的算法实现。

  2. 避免使用大量的条件语句:如果一个问题需要根据不同的条件来选择不同的算法实现,使用大量的条件语句会使得代码难以维护和扩展。使用策略模式可以将不同的算法实现封装为不同的策略类,避免使用大量的条件语句。

  3. 代码解耦:使用策略模式可以将算法实现与客户端代码解耦,使得客户端代码更加灵活,可以轻松地切换不同的算法实现。

  4. 可扩展性:策略模式可以轻松地添加新的算法实现,使得系统更加灵活和可扩展。

  5. 单一职责原则:策略模式可以使得每个策略类只负责一个算法实现,符合单一职责原则,使得代码更加清晰和易于维护。