支付模块设计:策略模式 + 模板方法模式的优雅实践

前言

在复杂的业务系统中,支付模块往往需要对接多种支付渠道(微信、支付宝、银联、模拟支付等)。如何设计一个高内聚、低耦合、易扩展的支付架构?本文结合一段真实的支付代码,分享策略模式与模板方法模式的组合实践。

一、核心设计目标

目标说明
统一接口所有支付渠道对外暴露一致的行为
代码复用公共逻辑(流水号生成、参数构建)不重复编写
差异隔离每个渠道的特殊逻辑独立在自己的类中
类型安全编译期检查,避免运行时类型错误

二、整体架构图

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);
    }
}

八、小结

本文展示的设计方案,通过策略模式实现支付渠道的灵活切换,通过抽象类实现公共逻辑的复用,通过接口定义统一契约。这种组合模式在实际项目中具有以下优势:

  1. 扩展性强:新增支付渠道只需添加一个子类

  2. 复用性高:公共逻辑在抽象类中只写一次

  3. 类型安全:泛型接口保证编译期类型检查

  4. 易于测试:每个策略可以独立单元测试

希望这篇文章对你在实际项目中的架构设计有所帮助。

本回答由 AI 生成,内容仅供参考,请仔细甄别。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐