设计模式-策略模式

什么是策略模式

策略模式(Strategy Pattern)定义了一系列算法,将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。

核心思想:消灭 if-else / switch-case,将「变化的行为」抽象成策略接口,每种策略独立实现,由上下文动态选择。

适用场景

  • 支付方式(微信、支付宝、银行卡)
  • 优惠券计算(满减、折扣、免运费)
  • 消息推送(短信、邮件、站内信)
  • 文件导出(Excel、PDF、CSV)
  • 登录方式(账号密码、手机验证码、第三方 OAuth)

传统写法的问题

public BigDecimal calcDiscount(String couponType, BigDecimal price) {
    if ("FULL_REDUCE".equals(couponType)) {
        return price.subtract(new BigDecimal("20"));
    } else if ("DISCOUNT".equals(couponType)) {
        return price.multiply(new BigDecimal("0.8"));
    } else if ("FREE_SHIPPING".equals(couponType)) {
        return price; // 减免运费逻辑
    }
    throw new IllegalArgumentException("未知优惠券类型: " + couponType);
}

问题

  1. 每新增一种类型,必须修改已有代码,违反开闭原则
  2. 逻辑膨胀,一个方法越来越长
  3. 无法复用,难以单测

Spring 最佳实践

1. 定义策略接口

public interface CouponStrategy {

    /**
     * 返回该策略对应的优惠券类型标识
     */
    String couponType();

    /**
     * 计算优惠后价格
     */
    BigDecimal calc(BigDecimal originalPrice);
}

关键设计:接口中包含 couponType() 方法,用于 Spring 自动注册时作为 Map 的 key。

2. 实现各策略(交给 Spring 管理)

@Component
public class FullReduceStrategy implements CouponStrategy {

    @Override
    public String couponType() {
        return "FULL_REDUCE";
    }

    @Override
    public BigDecimal calc(BigDecimal originalPrice) {
        return originalPrice.subtract(new BigDecimal("20"));
    }
}
@Component
public class DiscountStrategy implements CouponStrategy {

    @Override
    public String couponType() {
        return "DISCOUNT";
    }

    @Override
    public BigDecimal calc(BigDecimal originalPrice) {
        return originalPrice.multiply(new BigDecimal("0.8"))
                            .setScale(2, RoundingMode.HALF_UP);
    }
}
@Component
public class FreeShippingStrategy implements CouponStrategy {

    private static final BigDecimal SHIPPING_FEE = new BigDecimal("10");

    @Override
    public String couponType() {
        return "FREE_SHIPPING";
    }

    @Override
    public BigDecimal calc(BigDecimal originalPrice) {
        return originalPrice.subtract(SHIPPING_FEE).max(BigDecimal.ZERO);
    }
}

3. 策略工厂(核心:Spring 自动注入 List)

@Component
public class CouponStrategyFactory {

    private final Map<String, CouponStrategy> strategyMap;

    /**
     * Spring 会自动将所有 CouponStrategy 实现注入为 List
     * 在构造器中转为 Map,key = couponType()
     */
    public CouponStrategyFactory(List<CouponStrategy> strategies) {
        this.strategyMap = strategies.stream()
                .collect(Collectors.toUnmodifiableMap(
                        CouponStrategy::couponType,
                        Function.identity()
                ));
    }

    public CouponStrategy getStrategy(String couponType) {
        CouponStrategy strategy = strategyMap.get(couponType);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的优惠券类型: " + couponType);
        }
        return strategy;
    }
}

Spring 注入 Map 的另一种写法(直接注入,beanName 作为 key):

@Component
public class CouponStrategyFactory {

    // Spring 会以 beanName 为 key,自动填充 Map
    @Autowired
    private Map<String, CouponStrategy> strategyMap;

    public CouponStrategy getStrategy(String couponType) {
        return Optional.ofNullable(strategyMap.get(couponType + "Strategy"))
                       .orElseThrow(() -> new IllegalArgumentException("不支持: " + couponType));
    }
}

推荐第一种(List 转 Map),因为 key 由接口自己定义,不依赖 Bean 命名约定,更加健壮。

4. 服务层调用(零 if-else)

@Service
@RequiredArgsConstructor
public class OrderService {

    private final CouponStrategyFactory strategyFactory;

    public BigDecimal applyDiscount(String couponType, BigDecimal originalPrice) {
        return strategyFactory.getStrategy(couponType).calc(originalPrice);
    }
}

进阶:用枚举替代魔法字符串

public enum CouponType {
    FULL_REDUCE, DISCOUNT, FREE_SHIPPING;
}

策略接口改为枚举类型:

public interface CouponStrategy {
    CouponType couponType();
    BigDecimal calc(BigDecimal originalPrice);
}
@Component
public class FullReduceStrategy implements CouponStrategy {

    @Override
    public CouponType couponType() {
        return CouponType.FULL_REDUCE;
    }

    @Override
    public BigDecimal calc(BigDecimal originalPrice) {
        return originalPrice.subtract(new BigDecimal("20"));
    }
}

工厂:

@Component
public class CouponStrategyFactory {

    private final Map<CouponType, CouponStrategy> strategyMap;

    public CouponStrategyFactory(List<CouponStrategy> strategies) {
        this.strategyMap = strategies.stream()
                .collect(Collectors.toUnmodifiableMap(
                        CouponStrategy::couponType,
                        Function.identity()
                ));
    }

    public CouponStrategy getStrategy(CouponType type) {
        return Optional.ofNullable(strategyMap.get(type))
                       .orElseThrow(() -> new IllegalArgumentException("不支持的类型: " + type));
    }
}
本文作者:
本文链接: https://hgnulb.github.io/blog/2026/66579770
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!