0%

策略模式实战设计与可扩展支付架构

一、前言

作为一名在Java世界里摸爬滚打了十年的“老码农”,我见证过无数项目的兴起与重构。其中一个最常见的、也是最让人头疼的问题,就是随着业务爆炸式增长,代码中充斥着庞大而脆弱的 if-elseswitch-case 语句。

它们像代码的 “肿瘤”,初始时良性,但随着时间推移,变得难以维护、难以测试、难以扩展。

今天,我们就来深入探讨一下解决这个痛点的利器——策略模式。这不仅仅是教科书上的一个设计模式,更是我们日常开发中,应对业务多变性的核心架构手段。

一个真实的场景:从“屎山”代码说起

  • 假设我们正在开发一个电商平台的支付模块。最初,系统只支持支付宝支付。代码可能是这样的:
1
2
3
4
5
6
7
8
9
10
public class PaymentService {
public void pay(String orderId, String paymentType) {
if ("alipay".equals(paymentType)) {
// 调用支付宝SDK,组织参数,发起支付请求
System.out.println("使用支付宝支付订单:" + orderId);
// ... 一系列复杂的支付宝专用逻辑
}
// 后续可能还有日志记录、通知更新等操作
}
}
  • 业务发展迅猛,很快就要接入微信支付。你可能会这样修改:
1
2
3
4
5
6
7
8
9
10
11
12
public class PaymentService {
public void pay(String orderId, String paymentType) {
if ("alipay".equals(paymentType)) {
// ... 复杂的支付宝逻辑
System.out.println("使用支付宝支付订单:" + orderId);
} else if ("wechat_pay".equals(paymentType)) {
// ... 复杂的微信支付逻辑
System.out.println("使用微信支付订单:" + orderId);
}
// ...
}
}

紧接着,银联支付、Apple Pay、信用卡支付、会员积分抵扣等需求接踵而至。这个 pay 方法迅速膨胀成数百行的”巨无霸”,存在以下严重问题:

  • 违反开闭原则:每次新增支付方式都要修改核心类
  • 单一职责沦陷:一个方法承担过多职责
  • 测试复杂度高:条件分支组合爆炸,难以覆盖所有场景
  • 代码腐化:随着时间推移,逐渐演变为”屎山”代码

策略模式正是拯救这种架构腐化的银弹,它帮助我们实现关注点分离,构建可扩展的优雅架构。

二、策略模式的核心思想与架构

1. 官方定义

策略模式(Strategy Pattern)定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

2. 通俗理解

将策略模式想象成高级工具箱

  • 支付方式(支付宝、微信等)是不同的”专业工具”
  • PaymentService(使用者)不需要关心具体工具的内部机制
  • 需要更换工具?直接从工具箱选取即可,无需改造使用者的”手”

3. 模式结构

策略模式通常包含三个核心角色:

  • 环境类: 持有一个策略类的引用,最终给客户端调用。对应我们的 PaymentService
  • 抽象策略: 定义了所有具体策略需要实现的接口或抽象类。它规定了策略家族的行为规范。
  • 具体策略: 实现了抽象策略接口,提供了具体的算法实现。对应 AlipayStrategy, WechatPayStrategy 等。
1
2
3
4
5
6
┌─────────────────┐          ┌─────────────────────┐          ┌───────────────────┐
│ 环境类 │ │ 抽象策略 │ │ 具体策略 │
│ (Context) │ ───────> │ (Strategy) │ <─────── │ (Concrete │
│ PaymentService│ │ PaymentStrategy │ │ Strategy) │
│ │ │ │ │ AlipayStrategy │
└─────────────────┘ └─────────────────────┘ └───────────────────┘

下面,我们就用代码来重构上面的支付场景。

三、实战重构:构建高扩展的支付架构

步骤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
/**
* 支付策略接口
*/
public interface PaymentStrategy {

/**
* 支持的支付类型
*/
String getPaymentType();

/**
* 支付
* @param orderId 订单ID
* @param amount 支付金额
*/
PayResult pay(String orderId, BigDecimal amount);

/**
* 退款操作
* @param orderId 订单ID
* @param amount 退款金额
* @return 退款结果
*/
default RefundResult refund(String orderId, BigDecimal amount) {
throw new UnsupportedOperationException("该支付方式不支持退款");
}
}

这里我定义了一个 getPaymentType() 方法,用于后续根据类型查找策略,还定义了一个统一的 pay 方法。PayResult 是一个简单的支付结果封装类。

步骤2:实现具体策略(算法具体化)

现在,我们将每种支付方式独立成一个类,实现 PaymentStrategy 接口。

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
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
/**
* 支付宝支付策略
* 职责:封装所有支付宝相关的支付逻辑
*/
@Component
@Slf4j
public class AlipayStrategy implements PaymentStrategy {

private final AlipayClient alipayClient; // 注入支付宝官方客户端

public AlipayStrategy(AlipayClient alipayClient) {
this.alipayClient = alipayClient;
}

@Override
public String getPaymentType() {
return "alipay";
}

@Override
public PayResult pay(String orderId, BigDecimal amount) {
try {
log.info("发起支付宝支付,订单:{},金额:{}", orderId, amount);

// 1. 构建支付宝请求参数
AlipayTradePayRequest request = buildAlipayRequest(orderId, amount);

// 2. 调用支付宝SDK
AlipayTradePayResponse response = alipayClient.execute(request);

// 3. 处理响应
if ("10000".equals(response.getCode())) {
log.info("支付宝支付成功,订单:{}", orderId);
return new PayResult(200, "支付宝支付成功", orderId);
} else {
log.error("支付宝支付失败,订单:{},原因:{}", orderId, response.getMsg());
return new PayResult(500, "支付宝支付失败:" + response.getMsg(), orderId);
}
} catch (Exception e) {
log.error("支付宝支付异常,订单:{}", orderId, e);
return new PayResult(500, "支付宝支付异常:" + e.getMessage(), orderId);
}
}

@Override
public RefundResult refund(String orderId, BigDecimal amount) {
// 实现支付宝退款逻辑
return new RefundResult(200, "支付宝退款成功", orderId, amount);
}

private AlipayTradePayRequest buildAlipayRequest(String orderId, BigDecimal amount) {
// 构建支付宝请求参数
return new AlipayTradePayRequest();
}
}

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
/**
* 微信支付策略
* 职责:封装所有微信支付相关逻辑
*/
@Component
@Slf4j
public class WechatPayStrategy implements PaymentStrategy {

private final WxPayService wxPayService; // 注入微信支付服务

public WechatPayStrategy(WxPayService wxPayService) {
this.wxPayService = wxPayService;
}

@Override
public String getPaymentType() {
return "wechat_pay";
}

@Override
public PayResult pay(String orderId, BigDecimal amount) {
try {
log.info("发起微信支付,订单:{},金额:{}", orderId, amount);

// 微信支付特有的逻辑处理
WxPayUnifiedOrderRequest request = buildWechatRequest(orderId, amount);
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);

if ("SUCCESS".equals(result.getReturnCode())) {
log.info("微信支付成功,订单:{}", orderId);
return new PayResult(200, "微信支付成功", orderId);
} else {
log.error("微信支付失败,订单:{},原因:{}", orderId, result.getReturnMsg());
return new PayResult(500, "微信支付失败:" + result.getReturnMsg(), orderId);
}
} catch (Exception e) {
log.error("微信支付异常,订单:{}", orderId, e);
return new PayResult(500, "微信支付异常:" + e.getMessage(), orderId);
}
}

private WxPayUnifiedOrderRequest buildWechatRequest(String orderId, BigDecimal amount) {
// 构建微信支付请求参数
return new WxPayUnifiedOrderRequest();
}
}

步骤3:构建策略工厂(统一管理)

策略工厂是策略模式的核心协调者,负责策略的注册、管理和获取。

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
/**
* 支付策略工厂
*/
@Component
public class PaymentStrategyFactory {

/**
* 策略映射池: paymentType -> Strategy Bean
*/
private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();

/**
* 构造函数注入所有PaymentStrategy的实现类
* Spring会自动将所有实现PaymentStrategy接口的Bean注入到这个Map中
* Key是Bean的名字,但我们更希望用getPaymentType()作为Key
*/
public PaymentStrategyFactory(List<PaymentStrategy> strategies) {
for (PaymentStrategy strategy : strategies) {
strategyMap.put(strategy.getPaymentType(), strategy);
}
// 也可以手动放入,但上面的方式更自动化
// strategyMap.put("alipay", alipayStrategy);
// strategyMap.put("wechat_pay", wechatPayStrategy);
}

/**
* 根据支付类型获取策略
*/
public PaymentStrategy getStrategy(String paymentType) {
PaymentStrategy strategy = strategyMap.get(paymentType);
if (strategy == null) {
throw new RuntimeException("不支持的支付类型:" + paymentType);
}
return strategy;
}
}

这里利用了Spring的依赖注入特性,在工厂初始化时,自动将所有 PaymentStrategy 类型的Bean收集到一个Map中,Key就是我们定义的 getPaymentType() 返回值。

对于初始化成本高的策略,也可以实现懒加载机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class LazyInitPaymentStrategyFactory {

private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
private final ApplicationContext applicationContext;

public PaymentStrategy getStrategy(String paymentType) {
return strategyMap.computeIfAbsent(paymentType, this::createStrategy);
}

private PaymentStrategy createStrategy(String paymentType) {
// 根据类型创建策略,可以是Bean查找或动态实例化
return applicationContext.getBeansOfType(PaymentStrategy.class)
.values().stream()
.filter(s -> s.getPaymentType().equals(paymentType))
.findFirst()
.orElseThrow(() -> new RuntimeException("不支持的支付类型:" + paymentType));
}
}

步骤4:改造环境类(PaymentService)

现在,我们的 PaymentService 变得极其清爽和稳固。

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
/**
* 支付服务 - 环境类
*/
@Service
public class PaymentService {

// 注入策略工厂
private final PaymentStrategyFactory strategyFactory;

public PaymentService(PaymentStrategyFactory strategyFactory) {
this.strategyFactory = strategyFactory;
}

/**
* 统一的支付入口
*/
public PayResult executePay(String orderId, String paymentType, BigDecimal amount) {
// 1. 参数校验等前置工作...

// 2. 通过工厂获取策略,完全消除了if-else
PaymentStrategy strategy = strategyFactory.getStrategy(paymentType);

// 3. 执行支付策略
PayResult result = strategy.pay(orderId, amount);

// 4. 支付后置处理,如记录日志、发送消息等
System.out.println("支付完成,结果:" + result.getMessage());
return result;
}
}

PaymentService 支付方法通过工厂获取对应的策略进行支付,后期扩展支付类型,这块的代码完全不需要修改,只需要添加对应的策略实现类即可。

四、策略模式的威力与优势分析

经过这番重构,我们的代码发生了质的变化:

  • 符合开闭原则: 当需要新增一种支付方式(例如 BankCardStrategy)时,我们只需要新建一个类实现 PaymentStrategy 接口,并将其注册为Spring Bean即可。完全不需要修改 PaymentServicePaymentStrategyFactory 或任何其他已有的策略类。系统的扩展性极强。

  • 消除复杂条件判断: PaymentService 中的 if-else 彻底消失,代码可读性和可维护性大幅提升。

  • 算法独立化,易于理解和测试: 每种支付逻辑都被封闭在各自的策略类中,职责单一。我们可以非常轻松地对每个策略进行单元测试。

    1
    2
    3
    4
    5
    6
    @Test
    public void testAlipayStrategy() {
    AlipayStrategy strategy = new AlipayStrategy();
    PayResult result = strategy.pay("123", new BigDecimal("100.00"));
    assertEquals(200, result.getCode());
    }
  • 运行时动态切换策略: 客户端(如Controller)通过传入不同的 paymentType,就可以在运行时灵活地切换支付算法。

五、总结

策略模式的应用场景远不止支付,任何存在多种算法、业务规则或系统行为需要动态选择的场景,都是它的用武之地:

  • 营销优惠系统: 满减、折扣、赠品、优惠券等不同的优惠计算规则。
  • 数据导出系统: 导出为Excel、PDF、CSV等不同格式。
  • 消息通知系统: 通过短信、邮件、App推送、微信模板消息等不同渠道发送通知。
  • 排序算法: 根据不同条件(价格、销量、评分)使用不同的排序策略。
  • 权限校验: 用户名密码、短信验证码、第三方OAuth等多种登录方式。

回顾十年开发历程,我深刻体会到,设计模式并非炫技的“花拳绣腿”,而是应对软件复杂性的务实工具。策略模式教会我们的,是一种 “分而治之”“面向接口编程” 的架构思想。

当你的代码中出现以下“坏味道”时,请毫不犹豫地考虑策略模式:

  • 一个方法内部存在大量的条件判断分支。
  • 同一个算法在多个地方被复制粘贴。
  • 经常因为新增一种业务类型而修改核心业务类。

从满屏 if-else 的“屎山”代码,到结构清晰、扩展性强的优雅架构,策略模式为我们提供了一条清晰的演进路径。掌握它,不仅能让你写出更健壮的代码,更能让你在应对产品经理天马行空的需求时,多一份从容与自信。

记住,优秀的代码不是一次写成的,而是在不断的重构与思考中演化而来的。

您的打赏,是我创作的动力!不给钱?那我只能靠想象力充饥了。