# 策略模式:算法选择的智慧
# 一、业务痛点与技术背景
核心价值:解决电商支付系统中支付方式动态扩展、促销策略灵活配置等实际问题,掌握策略模式在复杂业务场景下的最佳实践,避免代码臃肿和维护困难。
在我们电商平台的支付模块重构中,最初只有支付宝和微信支付,代码相对简单。但随着业务发展,新增了银联、Apple Pay、Google Pay 等多种支付方式,原有的 if-else 结构迅速膨胀到 2000 + 行,每次新增支付方式都需要修改核心支付类,测试回归范围巨大,线上故障频发。
更糟糕的是,促销策略模块出现了同样的困境:满减、折扣、优惠券、组合优惠等策略相互嵌套,逻辑复杂度呈指数级增长。在一次大促活动中,由于策略逻辑错误,导致系统错误计算了约 50 万笔订单的优惠金额,直接损失超过 200 万元。
这些惨痛的教训让我们意识到:算法族的动态选择和扩展,需要更优雅的设计方案。
# 二、策略模式的核心原理
# 2.1 本质类比:工具箱思维
策略模式的本质就像一个智能工具箱。想象一个修车师傅的工具箱:
- 工具箱本身:就是上下文环境(Context),负责管理工具
- 各种工具:就是具体策略(Strategy),每个工具解决特定问题
- 师傅的选择:就是策略选择逻辑,根据问题类型选择合适工具
关键在于:工具箱不关心具体怎么修车,只负责提供正确的工具。这种分离让系统具备了 "热插拔" 算法的能力。
# 2.2 核心设计逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| public interface PaymentStrategy {
PaymentResult pay(PaymentRequest request);
String getPaymentType();
boolean validate(PaymentRequest request); }
@Component public class AlipayStrategy implements PaymentStrategy { @Override public PaymentResult pay(PaymentRequest request) { try { AlipayRequest alipayRequest = convertToAlipayRequest(request); AlipayResponse response = alipayClient.execute(alipayRequest); return convertToPaymentResult(response); } catch (AlipayException e) { log.error("支付宝支付失败", e); return PaymentResult.failure(e.getMessage()); } } @Override public String getPaymentType() { return "ALIPAY"; } @Override public boolean validate(PaymentRequest request) { return request.getAlipayUserId() != null && request.getAmount().compareTo(BigDecimal.ZERO) > 0; } }
@Service public class PaymentContext { private final Map<String, PaymentStrategy> strategyMap; public PaymentContext(List<PaymentStrategy> strategies) { this.strategyMap = strategies.stream() .collect(Collectors.toMap( PaymentStrategy::getPaymentType, Function.identity() )); } public PaymentResult executePayment(PaymentRequest request) { PaymentStrategy strategy = selectStrategy(request); if (!strategy.validate(request)) { return PaymentResult.failure("支付参数验证失败"); } return strategy.pay(request); } private PaymentStrategy selectStrategy(PaymentRequest request) { PaymentStrategy strategy = strategyMap.get(request.getPaymentType()); if (strategy == null) { throw new UnsupportedOperationException("不支持的支付方式: " + request.getPaymentType()); } return strategy; } }
|
工程师洞察:这种设计的精妙之处在于依赖倒置 —— 上下文依赖抽象接口,而不是具体实现。当新增支付方式时,只需实现 PaymentStrategy 接口,Spring 会自动注册到策略容器中,核心支付逻辑完全不需要修改。
# 三、实践方案:电商支付系统重构
# 3.1 场景约束与设计思路
适用场景:
- QPS 1 万以下的支付系统
- 支付方式需要频繁扩展
- 不同支付方式有独立的参数验证逻辑
- 需要支持支付方式的动态启用 / 禁用
不适用场景:
- 超高并发(QPS 10 万 +)的支付核心,需要考虑性能优化
- 支付逻辑极其简单,只有 2-3 种固定方式
# 3.2 具体实现步骤
第一步:策略接口设计
1 2 3 4 5 6 7
| public interface PaymentStrategy { PaymentResult pay(PaymentRequest request); String getPaymentType(); boolean validate(PaymentRequest request); boolean isEnabled(); int getPriority(); }
|
第二步:基础策略抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public abstract class AbstractPaymentStrategy implements PaymentStrategy { @Override public boolean validate(PaymentRequest request) { if (request == null) { return false; } if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) { return false; } if (request.getOrderId() == null || request.getOrderId().trim().isEmpty()) { return false; } return doValidate(request); } protected abstract boolean doValidate(PaymentRequest request); @Override public boolean isEnabled() { return true; } @Override public int getPriority() { return 0; } }
|
第三步:具体策略实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| @Component public class WechatPayStrategy extends AbstractPaymentStrategy { @Autowired private WechatPayClient wechatPayClient; @Override public PaymentResult pay(PaymentRequest request) { try { WxPayUnifiedOrderRequest wxRequest = buildWxPayRequest(request); WxPayUnifiedOrderResult wxResult = wechatPayClient.unifiedOrder(wxRequest); return PaymentResult.success(wxResult.getPrepayId()); } catch (WxPayException e) { log.error("微信支付失败: {}", e.getMessage()); return PaymentResult.failure("微信支付失败: " + e.getReturnMsg()); } } @Override public String getPaymentType() { return "WECHAT_PAY"; } @Override protected boolean doValidate(PaymentRequest request) { return request.getOpenId() != null && request.getTradeType() != null; } private WxPayUnifiedOrderRequest buildWxPayRequest(PaymentRequest request) { return WxPayUnifiedOrderRequest.newBuilder() .outTradeNo(request.getOrderId()) .totalFee(request.getAmount().multiply(BigDecimal.valueOf(100)).intValue()) .openid(request.getOpenId()) .tradeType(request.getTradeType()) .build(); } }
@Component public class BankCardStrategy extends AbstractPaymentStrategy { @Autowired private BankPaymentService bankPaymentService; @Override public PaymentResult pay(PaymentRequest request) { try { if (!performRiskCheck(request)) { return PaymentResult.failure("风控检查未通过"); } BankPaymentResult result = bankPaymentService.processPayment( request.getCardNumber(), request.getAmount(), request.getOrderId() ); return PaymentResult.success(result.getTransactionId()); } catch (BankPaymentException e) { log.error("银行卡支付失败", e); return PaymentResult.failure("银行卡支付失败: " + e.getMessage()); } } @Override public String getPaymentType() { return "BANK_CARD"; } @Override protected boolean doValidate(PaymentRequest request) { return isValidCardNumber(request.getCardNumber()) && isValidCVV(request.getCvv()) && isValidExpiryDate(request.getExpiryDate()); } private boolean performRiskCheck(PaymentRequest request) { return riskControlService.checkPaymentRisk(request); } }
|
第四步:策略管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Component public class PaymentStrategyManager { private final Map<String, PaymentStrategy> enabledStrategies; public PaymentStrategyManager(List<PaymentStrategy> allStrategies) { this.enabledStrategies = allStrategies.stream() .filter(PaymentStrategy::isEnabled) .sorted(Comparator.comparing(PaymentStrategy::getPriority)) .collect(Collectors.toMap( PaymentStrategy::getPaymentType, Function.identity(), (existing, replacement) -> existing, LinkedHashMap::new )); } public PaymentStrategy getStrategy(String paymentType) { PaymentStrategy strategy = enabledStrategies.get(paymentType); if (strategy == null) { throw new PaymentException("不支持的支付方式: " + paymentType); } return strategy; } public List<String> getAvailablePaymentTypes() { return new ArrayList<>(enabledStrategies.keySet()); } public void refreshStrategies(List<PaymentStrategy> allStrategies) { Map<String, PaymentStrategy> newStrategies = allStrategies.stream() .filter(PaymentStrategy::isEnabled) .sorted(Comparator.comparing(PaymentStrategy::getPriority)) .collect(Collectors.toMap( PaymentStrategy::getPaymentType, Function.identity(), (existing, replacement) -> existing, LinkedHashMap::new )); this.enabledStrategies.clear(); this.enabledStrategies.putAll(newStrategies); } }
|
# 3.3 效果验证
重构前后的对比数据:
| 指标 | 重构前 | 重构后 | 改善幅度 |
|---|
| 核心支付类代码行数 | 2100 行 | 300 行 | 减少 85.7% |
| 新增支付方式开发时间 | 3-5 天 | 0.5 天 | 减少 90% |
| 单元测试覆盖率 | 45% | 85% | 提升 88.9% |
| 线上支付故障率 | 0.15% | 0.02% | 降低 86.7% |
| 代码复杂度 (圈复杂度) | 25 | 8 | 降低 68% |
实践案例:在去年双十一期间,我们紧急需要上线数字人民币支付。使用策略模式后,整个开发过程只用了 4 小时:
- 实现 DigitalCurrencyStrategy 接口(1 小时)
- 编写单元测试(1 小时)
- 集成测试(1 小时)
- 配置上线(1 小时)
而按照传统方式,这个工作量至少需要 2 天,而且需要修改核心支付类,存在很大风险。
# 四、避坑指南:实战中的踩坑与解决方案
# 4.1 坑点一:策略选择逻辑过于复杂
问题描述:最初我们将策略选择逻辑放在 PaymentContext 中,导致选择逻辑与业务逻辑耦合,难以维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public PaymentResult executePayment(PaymentRequest request) { PaymentStrategy strategy; if (request.getAmount().compareTo(new BigDecimal("1000")) > 0) { if (request.getUserLevel() == UserLevel.VIP) { strategy = strategyMap.get("VIP_PAYMENT"); } else { strategy = strategyMap.get("NORMAL_PAYMENT"); } } else { if (request.isMobile()) { strategy = strategyMap.get("MOBILE_PAYMENT"); } else { strategy = strategyMap.get("WEB_PAYMENT"); } } return strategy.pay(request); }
|
根因分析:策略选择逻辑本身也是一个业务规则,应该独立出来,而不是硬编码在上下文中。
解决方案:引入策略选择器(Strategy Selector)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class PaymentStrategySelector { public String selectPaymentType(PaymentRequest request, List<String> availableTypes) { List<String> preferredTypes = calculatePreferredTypes(request); for (String preferredType : preferredTypes) { if (availableTypes.contains(preferredType)) { return preferredType; } } return availableTypes.get(0); } private List<String> calculatePreferredTypes(PaymentRequest request) { List<String> preferences = new ArrayList<>(); if (request.getUserLevel() == UserLevel.VIP) { preferences.add("QUICK_PAYMENT"); } if (request.isMobile()) { preferences.add("MOBILE_PAYMENT"); } if (request.getAmount().compareTo(new BigDecimal("5000")) > 0) { preferences.add("BANK_CARD"); } return preferences; } }
|
# 4.2 坑点二:策略实例管理不当
问题描述:在促销策略系统中,每个策略都需要访问数据库和缓存,但我们错误地使用了原型作用域,导致大量的数据库连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component @Scope("prototype") public class DiscountStrategy { @Autowired private DiscountRepository discountRepository; public BigDecimal calculateDiscount(Order order) { DiscountRule rule = discountRepository.findActiveRule(order.getUserId()); return rule.calculate(order); } }
|
根因分析:策略实例应该是单例的,只有状态数据才需要原型作用域。
解决方案:合理设计策略的作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Component public class DiscountStrategy { private final DiscountRepository discountRepository; private final DiscountCache discountCache; public DiscountStrategy(DiscountRepository discountRepository, DiscountCache discountCache) { this.discountRepository = discountRepository; this.discountCache = discountCache; } public BigDecimal calculateDiscount(Order order) { DiscountRule rule = discountCache.getRule(order.getUserId()) .orElseGet(() -> discountRepository.findActiveRule(order.getUserId())); return rule.calculate(order); } }
@Component @Scope("prototype") public class StatefulDiscountStrategy { private final Map<String, Object> context = new HashMap<>(); public void setContext(String key, Object value) { context.put(key, value); } public BigDecimal calculateDiscount(Order order) { return doCalculate(order, context); } }
|
# 4.3 坑点三:策略异常处理不统一
问题描述:不同策略的异常处理方式不一致,导致上层调用者难以统一处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class AlipayStrategy { public PaymentResult pay(PaymentRequest request) { try { return PaymentResult.success("success"); } catch (AlipayException e) { return PaymentResult.failure(e.getMessage()); } } }
public class WechatPayStrategy { public PaymentResult pay(PaymentRequest request) { if (!validate(request)) { throw new PaymentException("参数验证失败"); } return PaymentResult.success("success"); } }
|
根因分析:缺乏统一的异常处理规范。
解决方案:定义统一的异常处理策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public abstract class AbstractPaymentStrategy implements PaymentStrategy { @Override public final PaymentResult pay(PaymentRequest request) { try { preProcess(request); if (!validate(request)) { return PaymentResult.failure("参数验证失败"); } PaymentResult result = doPay(request); postProcess(request, result); return result; } catch (PaymentException e) { log.error("支付异常: {}", e.getMessage(), e); return PaymentResult.failure(e.getMessage()); } catch (Exception e) { log.error("系统异常", e); return PaymentResult.failure("系统异常,请稍后重试"); } } protected void preProcess(PaymentRequest request) { } protected abstract PaymentResult doPay(PaymentRequest request); protected void postProcess(PaymentRequest request, PaymentResult result) { } }
|
# 五、高级应用:策略模式的扩展
# 5.1 策略组合模式
在复杂的促销场景中,经常需要组合多个策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public interface PromotionStrategy { PromotionResult calculate(Order order); String getStrategyType(); boolean canCombine(); }
@Component public class PromotionCombiner { public PromotionResult calculateCombined(Order order, List<PromotionStrategy> strategies) { List<PromotionStrategy> combinableStrategies = strategies.stream() .filter(PromotionStrategy::canCombine) .collect(Collectors.toList()); BigDecimal totalDiscount = BigDecimal.ZERO; List<String> appliedStrategies = new ArrayList<>(); for (PromotionStrategy strategy : combinableStrategies) { PromotionResult result = strategy.calculate(order); if (result.isSuccess()) { totalDiscount = totalDiscount.add(result.getDiscountAmount()); appliedStrategies.add(strategy.getStrategyType()); } } return new PromotionResult(true, totalDiscount, appliedStrategies); } }
|
# 5.2 动态策略加载
支持运行时动态加载策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Component public class DynamicStrategyLoader { private final Map<String, PaymentStrategy> dynamicStrategies = new ConcurrentHashMap<>(); public void loadStrategy(String className, String strategyType) { try { Class<?> clazz = Class.forName(className); PaymentStrategy strategy = (PaymentStrategy) clazz.getDeclaredConstructor().newInstance(); dynamicStrategies.put(strategyType, strategy); log.info("动态加载策略成功: {} -> {}", strategyType, className); } catch (Exception e) { log.error("动态加载策略失败: {}", className, e); throw new StrategyLoadException("策略加载失败", e); } } public void unloadStrategy(String strategyType) { PaymentStrategy removed = dynamicStrategies.remove(strategyType); if (removed != null) { log.info("卸载策略成功: {}", strategyType); } } }
|
# 六、总结与延伸
# 6.1 核心观点提炼
策略模式的核心价值在于算法族的动态选择和扩展,它通过以下方式解决了复杂业务场景的问题:
- 解耦算法实现与算法使用:上下文不需要知道具体算法的实现细节
- 支持运行时算法切换:可以根据业务规则动态选择最优策略
- 符合开闭原则:新增策略不需要修改现有代码
- 提高代码可测试性:每个策略可以独立测试
# 6.2 技术演进趋势
策略模式在现代 Java 生态中的演进:
- 函数式编程结合:使用 Lambda 表达式简化策略实现
- 配置化策略:通过配置文件定义策略规则,实现无代码变更的策略调整
- AI 驱动的策略选择:基于机器学习算法自动选择最优策略
- Serverless 环境优化:在云函数场景下,策略可以独立部署和扩展
# 6.3 应用边界
策略模式不是银弹,在以下场景中需要谨慎使用:
- 性能敏感场景:策略选择和调用的开销可能影响性能
- 简单业务逻辑:过度设计会增加系统复杂度
- 策略间强依赖:如果策略之间有复杂的依赖关系,可能需要考虑其他模式
# 6.4 后续优化方向
- 性能优化:策略缓存、预编译、并行执行
- 监控完善:策略执行成功率、耗时统计
- 配置管理:策略的热更新、版本管理
- 错误恢复:策略失败时的降级和重试机制
策略模式作为行为型设计模式的典型代表,在处理复杂业务逻辑和算法选择时展现出强大的生命力。通过合理的设计和实践,它能够显著提升系统的可维护性和扩展性,是每个后端工程师都应该掌握的重要设计模式。