1. 策略模式初探:为什么我们需要它?
第一次接手老项目时,我面对满屏的if-else地狱差点崩溃。订单处理逻辑里嵌套了17层条件判断,每增加一个支付渠道就要修改核心业务类。这种经历让我深刻理解了策略模式的价值——它就像乐高积木,把会变的部分拆成可插拔的组件。
策略模式(Strategy Pattern)属于行为型设计模式,其核心在于定义算法族,将每个算法封装起来,使它们可以互相替换。这种模式让算法的变化独立于使用算法的客户端,在Java中通常通过接口+实现类的方式呈现。
关键认知:策略模式不是简单的"用接口替换if-else",而是通过组合关系将行为委托给策略对象,符合开闭原则(对扩展开放,对修改关闭)
2. 模式结构深度拆解
2.1 UML核心三要素
// 策略接口(抽象策略) public interface DiscountStrategy { BigDecimal calculate(BigDecimal amount); } // 具体策略实现 public class VIPDiscount implements DiscountStrategy { @Override public BigDecimal calculate(BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.8)); } } // 上下文环境类 public class OrderService { private DiscountStrategy strategy; public void setStrategy(DiscountStrategy strategy) { this.strategy = strategy; } public BigDecimal checkout(BigDecimal amount) { return strategy.calculate(amount); } }2.2 与状态模式的本质区别
新手常混淆策略模式和状态模式,二者UML相似但意图不同:
- 策略模式:客户端主动选择算法策略
- 状态模式:状态转换由内部条件触发,客户端无感知
3. 实战:电商促销系统改造
3.1 传统if-else实现
public BigDecimal applyDiscount(String userType, BigDecimal amount) { if ("VIP".equals(userType)) { return amount.multiply(0.8); } else if ("SVIP".equals(userType)) { return amount.multiply(0.7); } else { return amount; } }问题点:每次新增用户类型都要修改方法,违反开闭原则
3.2 策略模式改造步骤
- 定义策略接口
public interface DiscountStrategy { boolean support(String userType); BigDecimal apply(BigDecimal amount); }- 实现具体策略
public class VIPDiscount implements DiscountStrategy { @Override public boolean support(String userType) { return "VIP".equals(userType); } @Override public BigDecimal apply(BigDecimal amount) { return amount.multiply(BigDecimal.valueOf(0.8)); } }- 创建策略工厂
public class DiscountStrategyFactory { private static final List<DiscountStrategy> STRATEGIES = Arrays.asList( new VIPDiscount(), new SVIPDiscount(), new NewUserDiscount() ); public static DiscountStrategy getStrategy(String userType) { return STRATEGIES.stream() .filter(s -> s.support(userType)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("未知用户类型")); } }- 客户端调用
public BigDecimal checkout(String userType, BigDecimal amount) { DiscountStrategy strategy = DiscountStrategyFactory.getStrategy(userType); return strategy.apply(amount); }4. 高级应用技巧
4.1 Spring集成方案
// 策略接口标注注解 @StrategyType("discount") public interface DiscountStrategy { String getType(); // ... } // 自动注册策略 @Component public class StrategyRegistry { private final Map<String, DiscountStrategy> strategyMap = new ConcurrentHashMap<>(); @Autowired public StrategyRegistry(List<DiscountStrategy> strategies) { strategies.forEach(s -> strategyMap.put(s.getType(), s)); } public DiscountStrategy getStrategy(String type) { return Optional.ofNullable(strategyMap.get(type)) .orElseThrow(() -> new BusinessException("策略不存在")); } }4.2 组合策略模式
处理需要多个策略协同的场景:
public class CompositeDiscountStrategy implements DiscountStrategy { private final List<DiscountStrategy> strategies; public BigDecimal apply(BigDecimal amount) { BigDecimal result = amount; for (DiscountStrategy strategy : strategies) { result = strategy.apply(result); } return result; } }5. 性能优化与陷阱规避
5.1 策略对象复用
无状态策略可享元化:
public enum SingletonStrategy implements DiscountStrategy { INSTANCE; @Override public BigDecimal apply(BigDecimal amount) { return amount.multiply(0.9); } }5.2 常见反模式
- 策略类膨胀:单个策略类超过500行代码,应考虑拆分
- 上下文污染:避免在Context中保存策略无关的状态
- 过度设计:简单业务直接if-else更合适
黄金法则:当发现自己在复制粘贴条件分支逻辑时,就是引入策略模式的最佳时机
6. 测试策略模式
6.1 单元测试要点
class DiscountStrategyTest { @Test void should_apply_20per_discount() { DiscountStrategy strategy = new VIPDiscount(); BigDecimal result = strategy.apply(new BigDecimal("100")); assertEquals(0, result.compareTo(new BigDecimal("80"))); } @Test void should_throw_when_strategy_not_found() { assertThrows(IllegalArgumentException.class, () -> DiscountStrategyFactory.getStrategy("INVALID")); } }6.2 集成测试方案
@SpringBootTest class DiscountIntegrationTest { @Autowired private StrategyRegistry registry; @Test void should_apply_correct_discount() { DiscountStrategy strategy = registry.getStrategy("VIP"); // 验证策略行为 } }7. 模式演进与替代方案
7.1 Java8函数式实现
public class DiscountService { private final Map<String, Function<BigDecimal, BigDecimal>> strategies = Map.of( "VIP", amount -> amount.multiply(0.8), "SVIP", amount -> amount.multiply(0.7) ); public BigDecimal apply(String type, BigDecimal amount) { return strategies.getOrDefault(type, a -> a).apply(amount); } }7.2 规则引擎替代方案
对于超复杂规则系统,可考虑Drools等规则引擎:
KieSession kieSession = kieContainer.newKieSession(); kieSession.insert(order); kieSession.fireAllRules();在电商秒杀系统中,我们最终用策略模式管理了12种折扣策略和8种库存分配策略。当大促需要新增"前100名5折"策略时,只需新增一个策略类,核心流程完全不用修改。这种扩展性带来的开发效率提升,在频繁活动的业务场景下尤为珍贵。