# 策略模式:算法选择的智慧

# 一、业务痛点与技术背景

核心价值:解决电商支付系统中支付方式动态扩展、促销策略灵活配置等实际问题,掌握策略模式在复杂业务场景下的最佳实践,避免代码臃肿和维护困难。

在我们电商平台的支付模块重构中,最初只有支付宝和微信支付,代码相对简单。但随着业务发展,新增了银联、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 {
/**
* 执行支付
* @param request 支付请求
* @return 支付结果
*/
PaymentResult pay(PaymentRequest request);

/**
* 获取支付方式标识
*/
String getPaymentType();

/**
* 验证支付参数
*/
boolean validate(PaymentRequest request);
}

// 具体策略实现
@Component
public class AlipayStrategy implements PaymentStrategy {

@Override
public PaymentResult pay(PaymentRequest request) {
// 支付宝支付逻辑
try {
// 1. 参数预处理
AlipayRequest alipayRequest = convertToAlipayRequest(request);

// 2. 调用支付宝API
AlipayResponse response = alipayClient.execute(alipayRequest);

// 3. 结果转换
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) {
// Spring自动注入所有策略实现
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(
PaymentStrategy::getPaymentType,
Function.identity()
));
}

public PaymentResult executePayment(PaymentRequest request) {
// 1. 选择策略
PaymentStrategy strategy = selectStrategy(request);

// 2. 验证参数
if (!strategy.validate(request)) {
return PaymentResult.failure("支付参数验证失败");
}

// 3. 执行支付
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%
代码复杂度 (圈复杂度)258降低 68%

实践案例:在去年双十一期间,我们紧急需要上线数字人民币支付。使用策略模式后,整个开发过程只用了 4 小时:

  1. 实现 DigitalCurrencyStrategy 接口(1 小时)
  2. 编写单元测试(1 小时)
  3. 集成测试(1 小时)
  4. 配置上线(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<>();

// VIP用户优先使用快捷支付
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 核心观点提炼

策略模式的核心价值在于算法族的动态选择和扩展,它通过以下方式解决了复杂业务场景的问题:

  1. 解耦算法实现与算法使用:上下文不需要知道具体算法的实现细节
  2. 支持运行时算法切换:可以根据业务规则动态选择最优策略
  3. 符合开闭原则:新增策略不需要修改现有代码
  4. 提高代码可测试性:每个策略可以独立测试

# 6.2 技术演进趋势

策略模式在现代 Java 生态中的演进:

  1. 函数式编程结合:使用 Lambda 表达式简化策略实现
  2. 配置化策略:通过配置文件定义策略规则,实现无代码变更的策略调整
  3. AI 驱动的策略选择:基于机器学习算法自动选择最优策略
  4. Serverless 环境优化:在云函数场景下,策略可以独立部署和扩展

# 6.3 应用边界

策略模式不是银弹,在以下场景中需要谨慎使用:

  • 性能敏感场景:策略选择和调用的开销可能影响性能
  • 简单业务逻辑:过度设计会增加系统复杂度
  • 策略间强依赖:如果策略之间有复杂的依赖关系,可能需要考虑其他模式

# 6.4 后续优化方向

  1. 性能优化:策略缓存、预编译、并行执行
  2. 监控完善:策略执行成功率、耗时统计
  3. 配置管理:策略的热更新、版本管理
  4. 错误恢复:策略失败时的降级和重试机制

策略模式作为行为型设计模式的典型代表,在处理复杂业务逻辑和算法选择时展现出强大的生命力。通过合理的设计和实践,它能够显著提升系统的可维护性和扩展性,是每个后端工程师都应该掌握的重要设计模式。