设计模式-策略模式
什么是策略模式
策略模式(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);
}
问题:
- 每新增一种类型,必须修改已有代码,违反开闭原则
- 逻辑膨胀,一个方法越来越长
- 无法复用,难以单测
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));
}
}
本博客所有文章除特别声明外,均采用
CC BY-NC-SA 4.0
许可协议,转载请注明出处!