Skip to content

04 策略模式(Strategy)🎯

目标:理解策略模式如何替代复杂 if-else,让规则扩展更平滑、代码更易维护。


1. 模式定义

策略模式:把一组可互换的算法(规则)分别封装起来,在运行时按需选择具体策略执行。


2. 为什么不直接 if-else

直接 if-else 在小场景没问题,但当规则增多时会出现典型痛点:

  • 分支越来越长,可读性下降
  • 新增规则要改原方法,违反开闭原则
  • 不同规则逻辑耦合,测试困难
  • 容易引入“改一个分支影响其他分支”的回归问题

适合继续 if-else 的情况:

  • 规则只有 2~3 条且长期稳定
  • 代码是一次性脚本或临时逻辑

适合策略模式的情况:

  • 规则数量持续增长(如优惠、风控、路由、计费)
  • 需要按配置或上下文动态切换规则
  • 每个规则都希望独立测试、独立演进

3. 适用场景

真实业务中高频出现策略模式:

  • 电商优惠计算(满减、折扣、会员价、券后价)
  • 支付路由策略(优先微信/支付宝/银行卡)
  • 物流计费策略(按重量、按体积、按区域)
  • 风控策略(黑名单、频次限制、设备评分)
  • 导出策略(CSV、Excel、PDF)

4. Java 实现

下面用“订单优惠计算”演示。

4.1 策略接口

java
import java.math.BigDecimal;

public interface DiscountStrategy {
    BigDecimal apply(BigDecimal originalAmount);
}

4.2 具体策略实现

java
import java.math.BigDecimal;

public class NoDiscountStrategy implements DiscountStrategy {
    @Override
    public BigDecimal apply(BigDecimal originalAmount) {
        return originalAmount;
    }
}
java
import java.math.BigDecimal;

public class PercentageDiscountStrategy implements DiscountStrategy {
    private final BigDecimal rate; // 例如 0.9 代表 9 折

    public PercentageDiscountStrategy(BigDecimal rate) {
        this.rate = rate;
    }

    @Override
    public BigDecimal apply(BigDecimal originalAmount) {
        return originalAmount.multiply(rate);
    }
}
java
import java.math.BigDecimal;

public class FullReductionStrategy implements DiscountStrategy {
    private final BigDecimal threshold;
    private final BigDecimal reduction;

    public FullReductionStrategy(BigDecimal threshold, BigDecimal reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }

    @Override
    public BigDecimal apply(BigDecimal originalAmount) {
        if (originalAmount.compareTo(threshold) >= 0) {
            return originalAmount.subtract(reduction);
        }
        return originalAmount;
    }
}

4.3 上下文类

java
import java.math.BigDecimal;

public class DiscountContext {
    private DiscountStrategy strategy;

    public DiscountContext(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

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

    public BigDecimal calculate(BigDecimal amount) {
        return strategy.apply(amount);
    }
}

4.4 调用示例

java
import java.math.BigDecimal;

public class StrategyDemo {
    public static void main(String[] args) {
        BigDecimal amount = new BigDecimal("299");

        DiscountContext context = new DiscountContext(new NoDiscountStrategy());
        System.out.println("无优惠: " + context.calculate(amount));

        context.setStrategy(new PercentageDiscountStrategy(new BigDecimal("0.9")));
        System.out.println("9折后: " + context.calculate(amount));

        context.setStrategy(new FullReductionStrategy(
                new BigDecimal("200"),
                new BigDecimal("30")
        ));
        System.out.println("满减后: " + context.calculate(amount));
    }
}

5. 优缺点

5.1 优点

  • 消除大段 if-else,逻辑更清晰
  • 新增策略时,通常只需新增类
  • 每个策略可单测,质量更可控
  • 运行时可动态替换策略

5.2 缺点

  • 类数量会增多
  • 需要调用方理解策略选择逻辑
  • 小场景硬上策略会过度设计

6. 源码映射

6.1 JDK

  • Comparator:典型策略接口,传入不同比较策略即可改变排序行为。
java
list.sort((a, b) -> a.getAge() - b.getAge()); // 一种策略
list.sort((a, b) -> a.getName().compareTo(b.getName())); // 另一种策略

6.2 Spring

  • Resource 体系、HandlerMethodArgumentResolver 等场景,本质也是“面向接口 + 多实现 + 运行时选择”。
flowchart LR
    A["业务请求"] --> B["策略上下文"]
    B --> C{"选择策略"}
    C --> D["策略A"]
    C --> E["策略B"]
    C --> F["策略C"]
    D --> G["返回结果"]
    E --> G
    F --> G

7. 面试考点

7.1 策略模式和工厂模式区别

  • 策略模式:关注“算法/行为可替换”
  • 工厂模式:关注“对象创建过程封装”

常见组合:用工厂创建策略,再交给上下文执行。

7.2 策略模式和状态模式区别

  • 策略模式:策略通常由外部主动选择
  • 状态模式:状态常由内部状态流转自动切换

7.3 如何避免策略类爆炸

  • 对稳定规则用枚举 + lambda
  • 对复杂规则保留独立类
  • 引入“策略注册表”统一管理

8. 练习题

  1. 实现“运费计算策略”:普通、加急、同城即时达。
  2. 把一个现有 if-else 业务逻辑重构为策略模式。
  3. 结合工厂模式实现“按渠道名获取策略”的注册机制。

9. 小结

  • 策略模式最核心价值是:把变化的规则从主流程里拆出来
  • 当你发现 if-else 持续膨胀,就是策略模式该上场的信号。
  • 但也别滥用,规则简单稳定时,直接写清楚更好。