重构案例实战(中):支付网关集成重构
好,咱们接着聊支付网关的重构。上一章我们把问题摊开了——硬编码、if-else 堆成山、每次加新支付方式都要改老代码。今天我就带你亲手拆掉这座“屎山”,用策略模式加工厂模式,把它改造成一个可扩展、好维护的支付网关。
现状回顾:那段让人头疼的代码
先看一眼我们当时的“杰作”:
public class PaymentService {
public void pay(String type, double amount) {
if ("alipay".equals(type)) {
// 调用支付宝SDK
AlipayApi alipay = new AlipayApi();
alipay.setAppId("xxx");
alipay.setSecret("yyy");
alipay.pay(amount);
} else if ("wechat".equals(type)) {
// 调用微信支付SDK
WechatPayApi wechat = new WechatPayApi();
wechat.setMchId("zzz");
wechat.setKey("kkk");
wechat.transfer(amount);
} else if ("unionpay".equals(type)) {
// 调用银联支付SDK
UnionPayApi union = new UnionPayApi();
union.setMerchant("mmm");
union.pay(amount);
} else {
throw new UnsupportedOperationException("不支持的支付方式");
}
}
}
这段代码的问题,我总结过三点:
- 违背开闭原则——加一种支付方式,就得改这个类
- 代码重复——每个分支都在做“初始化SDK + 调用支付”
- 测试困难——想测支付宝逻辑,得把整个if-else跑一遍
我在一个电商项目里见过类似的代码,当时线上出了个bug——微信支付接口升级,需要改签名方式。结果改完微信,不小心把支付宝的配置也带偏了。嗯,这种耦合的痛,经历过的人都懂。
重构第一步:定义支付策略接口
策略模式的核心,就是把“怎么做”和“谁来做”分开。我们先定义一个统一的支付策略接口:
public interface PaymentStrategy {
void pay(double amount);
}
然后每个支付方式实现这个接口:
public class AlipayStrategy implements PaymentStrategy {
private AlipayApi alipay;
public AlipayStrategy() {
this.alipay = new AlipayApi();
alipay.setAppId("xxx");
alipay.setSecret("yyy");
}
@Override
public void pay(double amount) {
alipay.pay(amount);
}
}
public class WechatPayStrategy implements PaymentStrategy {
private WechatPayApi wechat;
public WechatPayStrategy() {
this.wechat = new WechatPayApi();
wechat.setMchId("zzz");
wechat.setKey("kkk");
}
@Override
public void pay(double amount) {
wechat.transfer(amount);
}
}
public class UnionPayStrategy implements PaymentStrategy {
private UnionPayApi union;
public UnionPayStrategy() {
this.union = new UnionPayApi();
union.setMerchant("mmm");
}
@Override
public void pay(double amount) {
union.pay(amount);
}
}
你看,每个策略类只关心自己的初始化逻辑和支付调用。测试的时候,我可以单独 mock 一个策略来测,不用再担心牵一发动全身。
重构第二步:引入工厂模式
策略定义好了,但客户端怎么拿到正确的策略?你可能会想:再写个 if-else 工厂?那不就换汤不换药了吗?
我个人习惯用注册式工厂,配合一个 Map 来管理策略实例:
public class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
static {
strategies.put("alipay", new AlipayStrategy());
strategies.put("wechat", new WechatPayStrategy());
strategies.put("unionpay", new UnionPayStrategy());
}
public static PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = strategies.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + type);
}
return strategy;
}
}
这样一来,客户端代码就变成了:
public class PaymentService {
public void pay(String type, double amount) {
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(type);
strategy.pay(amount);
}
}
是不是清爽多了?原来那个几十行的 if-else,现在只剩两行。而且,加一种新支付方式,只需要:
- 新建一个策略类实现
PaymentStrategy - 在工厂的
static块里注册一下
你想想看,这改动完全不会影响已有的支付逻辑。开闭原则,就这么实现了。
核心变化对比
| 维度 | 重构前 | 重构后 |
|---|---|---|
| 代码行数 | 30+ 行 if-else | 每个策略类约10行 + 工厂类15行 |
| 扩展方式 | 修改 PaymentService | 新增策略类 + 注册 |
| 测试难度 | 需要模拟整个流程 | 可单独测试每个策略 |
| 耦合度 | 高(支付逻辑与业务逻辑耦合) | 低(策略独立) |
重构第三步:用 SVG 看清整体结构
光说可能不够直观,我画了一张图,把重构后的架构展示出来:
从图上能清楚看到:客户端只依赖工厂,工厂负责创建策略,每个策略独立实现支付逻辑。这就是“面向接口编程”的典型实践。
避坑指南:我曾经踩过的几个坑
重构不是一蹴而就的,有些细节不注意,反而会引入新问题。我分享几个亲身经历:
坑1:策略实例的线程安全问题
我曾经在工厂里直接 new 策略对象,然后多个线程共用同一个实例。结果支付宝SDK内部有状态,导致并发支付时数据错乱。后来我改成每次调用都创建新实例,或者确保策略类是无状态的。
建议:策略类尽量设计成无状态
把支付参数(比如金额、订单号)通过方法参数传入,而不是存在策略对象的字段里。这样同一个实例可以安全地被多个线程复用。
坑2:工厂注册方式太死板
一开始我用 static 块硬编码注册,后来发现每次加支付方式都要改工厂类。虽然比改 if-else 好,但还是不够灵活。更好的做法是用 Spring 的依赖注入,或者用 SPI 机制自动发现策略实现。
进阶方案:结合 Spring 的自动注册
如果你在用 Spring,可以让每个策略类实现 InitializingBean,在 afterPropertiesSet() 里把自己注册到工厂的 Map 中。这样加新支付方式时,连工厂类都不用改。
总结一下今天的核心收获
这次重构,我们做了三件事:
- 用策略模式——把每种支付方式封装成独立的策略类
- 用工厂模式——统一管理策略的创建和获取
- 解耦了业务逻辑和支付逻辑——加新支付方式不再需要改老代码
说白了,重构不是为了炫技,而是为了让代码更容易理解和修改。你想想看,如果半年后有个新同事接手这个模块,他看到的是清晰的策略类和工厂类,而不是一坨 if-else,是不是会感激你?
嗯,下一章我们继续深入,看看如何用单元测试来验证这次重构的正确性。到时候我会分享一些“测试替身”的小技巧,保证你学了就能用。
公众号:蓝海资料掘金营,微信deep3321