支付模块设计模式实现
·
支付模块设计:策略模式 + 模板方法模式的优雅实践
前言
在复杂的业务系统中,支付模块往往需要对接多种支付渠道(微信、支付宝、银联、模拟支付等)。如何设计一个高内聚、低耦合、易扩展的支付架构?本文结合一段真实的支付代码,分享策略模式与模板方法模式的组合实践。
一、核心设计目标
| 目标 | 说明 |
|---|---|
| 统一接口 | 所有支付渠道对外暴露一致的行为 |
| 代码复用 | 公共逻辑(流水号生成、参数构建)不重复编写 |
| 差异隔离 | 每个渠道的特殊逻辑独立在自己的类中 |
| 类型安全 | 编译期检查,避免运行时类型错误 |
二、整体架构图
text
┌─────────────────────────────────────────────────────────────┐
│ interface PayHandler │
│ (策略接口:定义所有支付渠道必须提供的能力) │
├─────────────────────────────────────────────────────────────┤
│ + payTypes() : PayTypes │
│ + tradeTypes() : List<TradeTypes> │
│ + doPrePay() : PrePayResult │
│ + doRefund() : RefundResult │
│ + doPayNotify() : PayNotifyResult │
│ + buildPrePayParam() : PrePayParam ← 接口定义 │
│ + buildRefundParam() : RefundParam ← 接口定义 │
└─────────────────────────────────────────────────────────────┘
▲
│ implements
│
┌─────────────────────────────────────────────────────────────┐
│ abstract class AbstractPayHandler │
│ (抽象类:选择性实现接口方法,提供公共工具方法) │
├─────────────────────────────────────────────────────────────┤
│ ✅ 实现 buildPrePayParam() - 公共参数构建逻辑 │
│ ✅ 实现 buildRefundParam() - 公共退款参数构建 │
│ ✅ 实现 resolvePrepayResult() - 公共结果解析 │
│ ✅ 提供 buildTradeNo() - 公共流水号生成 │
│ ❌ 未实现 doPrePay() - 留给子类 │
│ ❌ 未实现 doRefund() - 留给子类 │
│ ❌ 未实现 payTypes() - 留给子类 │
└─────────────────────────────────────────────────────────────┘
▲
│ extends
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ WechatPayHandler│ │ AlipayHandler │ │SimulatePayHandler│
│ (微信支付) │ │ (支付宝) │ │ (模拟支付) │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ 实现 doPrePay() │ │ 实现 doPrePay() │ │ 实现 doPrePay() │
│ 实现 payTypes() │ │ 实现 payTypes() │ │ 实现 payTypes() │
│ 重写参数构建 │ │ 重写参数构建 │ │ 重写参数构建 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
三、设计模式解析
3.1 策略模式(核心)
角色定位:将不同的支付渠道封装为独立的策略,可以动态切换。
java
// 策略接口
public interface PayHandler {
PayTypes payTypes(); // 策略标识
PrePayResult doPrePay(PrePayParam param);
RefundResult doRefund(RefundParam refundParam);
}
// 策略上下文(工厂获取策略)
@Service
public class PayContext {
private Map<PayTypes, PayHandler> strategyMap;
public PayHandler getStrategy(PayTypes payType) {
return strategyMap.get(payType);
}
}
3.2 模板方法模式的变体
与传统模板方法模式不同,这里的抽象类没有定义固定的算法骨架,而是提供了可复用的公共方法,子类可以:
-
直接继承使用
-
重写覆盖
-
在重写中调用
super扩展
java
public abstract class AbstractPayHandler implements PayHandler {
// 公共方法:子类可直接使用,也可重写扩展
public PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam) {
PrePayParam param = new PrePayParam();
param.setTradeNo(buildTradeNo("TRADE")); // 公共逻辑
param.setAmount(payBizParam.getAmount()); // 公共逻辑
return param;
}
// 工具方法:子类可直接使用
protected String buildTradeNo(String codeType) {
return redisSerialGenerator.generateIncrNo(codeType);
}
}
3.3 工厂模式
根据支付类型获取对应的处理器实例。
java
@Component
public class PayHandlerFactory {
@Autowired
private List<PayHandler> payHandlers; // Spring 自动注入所有实现
private Map<PayTypes, PayHandler> handlerMap;
@PostConstruct
public void init() {
handlerMap = payHandlers.stream()
.collect(Collectors.toMap(PayHandler::payTypes, Function.identity()));
}
public PayHandler getHandler(PayTypes payType) {
return handlerMap.get(payType);
}
}
四、接口与抽象类的职责划分
4.1 接口定义"契约"
java
public interface PayHandler {
// 声明能力,不关心如何实现
PayTypes payTypes();
PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam);
PrePayResult doPrePay(PrePayParam param);
}
4.2 抽象类提供"复用"
java
public abstract class AbstractPayHandler implements PayHandler {
// ✅ 实现公共逻辑,子类无需重复
@Override
public PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam) {
// 公共参数构建逻辑
PrePayParam param = new PrePayParam();
param.setTradeNo(buildTradeNo("TRADE"));
param.setAmount(payBizParam.getAmount());
return param;
}
// ✅ 提供工具方法
protected String buildTradeNo(String codeType) {
return redisSerialGenerator.generateIncrNo(codeType);
}
// ❌ 不实现,留给子类
public abstract PrePayResult doPrePay(PrePayParam param);
}
4.3 子类实现差异化
java
@Component
public class SimulatePayHandler extends AbstractPayHandler {
// 必须实现:接口中未由抽象类实现的方法
@Override
public PayTypes payTypes() {
return PayTypes.SIMULATE;
}
@Override
public PrePayResult doPrePay(PrePayParam param) {
// 模拟支付的实现
return simulatePay(param);
}
// 可选:重写并扩展父类方法
@Override
public PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam) {
// 1. 复用父类公共逻辑
PrePayParam param = super.buildPrePayParam(payType, payBizParam);
// 2. 添加模拟支付特有逻辑
param.setNotifyUrl(buildNotifyUrl());
return param;
}
}
五、关键设计问答
Q1:抽象类必须实现接口的所有方法吗?
不需要。 抽象类可以有选择地实现接口方法,未实现的方法留给子类实现。
java
// ✅ 合法:抽象类只实现部分接口方法
public abstract class AbstractPayHandler implements PayHandler {
@Override
public PrePayParam buildPrePayParam(...) { ... } // 实现
// payTypes() 未实现,留给子类
}
Q2:子类可以重写父类已实现的方法吗?
可以。 除非父类方法使用 final 修饰。
java
// 子类重写并扩展父类方法
@Override
public PrePayParam buildPrePayParam(...) {
PrePayParam param = super.buildPrePayParam(...); // 复用
param.setExtra(extraLogic()); // 扩展
return param;
}
Q3:子类可以不重写父类已实现的方法吗?
可以。 直接继承使用父类的默认实现。
java
// 微信支付直接使用父类的 buildPrePayParam,不重写
public class WechatPayHandler extends AbstractPayHandler {
// 不需要写 buildPrePayParam,直接用父类的
}
Q4:为什么接口要定义 buildPrePayParam 这样的方法?
因为接口是契约,定义"能做什么"。如果调用方需要先获取参数做处理(如缓存、展示),再调用支付,这个方法就必须暴露在接口中。
六、设计原则总结
| 原则 | 体现 |
|---|---|
| 开闭原则 | 新增支付渠道只需添加子类,无需修改现有代码 |
| 里氏替换 | 所有子类可以替换父类位置使用 |
| 接口隔离 | 接口方法按职责划分,不臃肿 |
| 单一职责 | 接口定义契约,抽象类提供复用,子类实现差异 |
| 依赖倒置 | 依赖 PayHandler 接口,不依赖具体实现 |
七、完整代码示例
接口定义
java
public interface PayHandler {
PayTypes payTypes();
List<TradeTypes> tradeTypes();
PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam);
PrePayResult doPrePay(PrePayParam param);
RefundParam buildRefundParam(PayTypes payType, RefundBizParam param);
RefundResult doRefund(RefundParam refundParam);
}
抽象类实现
java
@Slf4j
public abstract class AbstractPayHandler implements PayHandler {
@Autowired
protected RedisSerialGenerator redisSerialGenerator;
@Override
public PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam) {
PrePayParam param = new PrePayParam();
param.setPayType(payType)
.setTradeNo(buildTradeNo("TRADE"))
.setAmount(payBizParam.getAmount())
.setClientIp(payBizParam.getClientIp());
return param;
}
@Override
public RefundParam buildRefundParam(PayTypes payType, RefundBizParam param) {
RefundParam refundParam = new RefundParam();
refundParam.setRefundNo(buildTradeNo("REFUND"))
.setRefundAmount(param.getAmount());
return refundParam;
}
protected String buildTradeNo(String codeType) {
return redisSerialGenerator.generateIncrNo(codeType);
}
}
具体策略实现
java
@Component
public class SimulatePayHandler extends AbstractPayHandler {
@Override
public PayTypes payTypes() {
return PayTypes.SIMULATE;
}
@Override
public List<TradeTypes> tradeTypes() {
return List.of(TradeTypes.MINIAPP, TradeTypes.APP);
}
@Override
public PrePayResult doPrePay(PrePayParam param) {
log.info("模拟支付,参数:{}", param);
return PrePayResult.success();
}
@Override
public RefundResult doRefund(RefundParam refundParam) {
log.info("模拟退款,参数:{}", refundParam);
return RefundResult.success();
}
@Override
public PrePayParam buildPrePayParam(PayTypes payType, PayBizParam payBizParam) {
// 复用父类逻辑 + 扩展
PrePayParam param = super.buildPrePayParam(payType, payBizParam);
// 模拟支付特有:设置回调地址
param.setNotifyUrl(buildNotifyUrl(payBizParam));
return param;
}
private String buildNotifyUrl(PayBizParam param) {
return String.format("https://%s/notify/%d",
param.getNotifyDomain(), param.getMerchantId());
}
}
工厂与使用
java
@Component
public class PayHandlerFactory {
private final Map<PayTypes, PayHandler> handlerMap;
public PayHandlerFactory(List<PayHandler> handlers) {
this.handlerMap = handlers.stream()
.collect(Collectors.toMap(PayHandler::payTypes, Function.identity()));
}
public PayHandler getHandler(PayTypes payType) {
PayHandler handler = handlerMap.get(payType);
if (handler == null) {
throw new ServiceException("不支持的支付类型: " + payType);
}
return handler;
}
}
@Service
public class PayService {
@Autowired
private PayHandlerFactory factory;
public PrePayResult pay(PayTypes payType, PayBizParam bizParam) {
PayHandler handler = factory.getHandler(payType);
PrePayParam param = handler.buildPrePayParam(payType, bizParam);
return handler.doPrePay(param);
}
}
八、小结
本文展示的设计方案,通过策略模式实现支付渠道的灵活切换,通过抽象类实现公共逻辑的复用,通过接口定义统一契约。这种组合模式在实际项目中具有以下优势:
-
扩展性强:新增支付渠道只需添加一个子类
-
复用性高:公共逻辑在抽象类中只写一次
-
类型安全:泛型接口保证编译期类型检查
-
易于测试:每个策略可以独立单元测试
希望这篇文章对你在实际项目中的架构设计有所帮助。
本回答由 AI 生成,内容仅供参考,请仔细甄别。
更多推荐
所有评论(0)