聚合系统设计:策略模式(Strategy Pattern)在银行通道对接场景中的应用
Java策略模式:银行接口对接场景示例
📊 策略模式简介
策略模式(Strategy Pattern)定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。
银行接口对接场景需求:
假设我们需要对接多家银行的支付接口,每家银行的支付方式、签名算法、通信协议都不同,但对外提供统一的支付接口。
🎯 完整示例代码
1. 策略接口定义
/**
* 银行支付策略接口
* 定义所有银行支付都需要实现的方法
*/
public interface BankPaymentStrategy {
/**
* 支付接口
* @param paymentRequest 支付请求参数
* @return 支付结果
*/
PaymentResult pay(PaymentRequest paymentRequest);
/**
* 查询支付状态
* @param orderNo 订单号
* @return 支付状态
*/
PaymentStatus queryStatus(String orderNo);
/**
* 退款接口
* @param refundRequest 退款请求
* @return 退款结果
*/
RefundResult refund(RefundRequest refundRequest);
/**
* 获取银行编码
* @return 银行编码
*/
String getBankCode();
}
2. 策略实现类(具体银行)
工商银行策略
点击查看代码
/**
* 工商银行支付策略实现
*/
@Component("ICBCPaymentStrategy")
public class ICBCPaymentStrategy implements BankPaymentStrategy {
private static final String BANK_CODE = "ICBC";
@Override
public PaymentResult pay(PaymentRequest paymentRequest) {
// 工商银行特有的支付逻辑
System.out.println("使用工商银行支付策略进行支付");
// 1. 构建工商银行特定的请求参数
ICBCPaymentRequest icbcRequest = buildICBCRequest(paymentRequest);
// 2. 使用工商银行的签名算法
String sign = generateICBCSign(icbcRequest);
icbcRequest.setSign(sign);
// 3. 调用工商银行接口(示例:HTTP调用)
String response = callICBCApi(icbcRequest);
// 4. 解析响应并返回统一格式
return parseICBCResponse(response);
}
@Override
public PaymentStatus queryStatus(String orderNo) {
System.out.println("使用工商银行策略查询订单状态");
// 工商银行特有的查询逻辑
ICBCQueryRequest request = new ICBCQueryRequest(orderNo);
request.setMerchantId("your_icbc_merchant_id");
request.setSign(generateICBCSign(request));
String response = callICBCQueryApi(request);
return parseICBCStatus(response);
}
@Override
public RefundResult refund(RefundRequest refundRequest) {
System.out.println("使用工商银行策略进行退款");
// 工商银行特有的退款逻辑
ICBCRefundRequest request = new ICBCRefundRequest(refundRequest);
request.setSign(generateICBCSign(request));
String response = callICBCRefundApi(request);
return parseICBCRefundResponse(response);
}
@Override
public String getBankCode() {
return BANK_CODE;
}
// 工商银行特有方法
private ICBCPaymentRequest buildICBCRequest(PaymentRequest request) {
ICBCPaymentRequest icbcRequest = new ICBCPaymentRequest();
icbcRequest.setAmount(request.getAmount());
icbcRequest.setOrderNo(request.getOrderNo());
icbcRequest.setMerchantId("your_icbc_merchant_id");
icbcRequest.setNotifyUrl("https://your-domain.com/notify/icbc");
return icbcRequest;
}
private String generateICBCSign(Object request) {
// 工商银行特有的签名算法
return "ICBC_SIGN_" + System.currentTimeMillis();
}
private String callICBCApi(ICBCPaymentRequest request) {
// 调用工商银行API
System.out.println("调用工商银行支付接口: " + request);
return "{\"code\":\"0000\",\"msg\":\"成功\",\"icbcOrderId\":\"ICBC" + System.currentTimeMillis() + "\"}";
}
private PaymentResult parseICBCResponse(String response) {
// 解析工商银行响应
System.out.println("解析工商银行响应: " + response);
return PaymentResult.success("ICBC" + System.currentTimeMillis());
}
// 其他工商银行特有方法...
}
建设银行策略
点击查看代码
/**
* 建设银行支付策略实现
*/
@Component("CCBPaymentStrategy")
public class CCBPaymentStrategy implements BankPaymentStrategy {
private static final String BANK_CODE = "CCB";
@Override
public PaymentResult pay(PaymentRequest paymentRequest) {
// 建设银行特有的支付逻辑
System.out.println("使用建设银行支付策略进行支付");
// 1. 使用建设银行的加密方式
String encryptedData = encryptWithCCB(paymentRequest);
// 2. 构建建设银行请求头
Map<String, String> headers = buildCCBHeaders();
// 3. 调用建设银行接口(可能使用不同的协议)
String response = callCCBApi(encryptedData, headers);
// 4. 建设银行特有的响应解析
return parseCCBResponse(response);
}
@Override
public PaymentStatus queryStatus(String orderNo) {
System.out.println("使用建设银行策略查询订单状态");
// 建设银行特有的查询逻辑
CCBQueryRequest request = new CCBQueryRequest();
request.setOrderNo(orderNo);
request.setTimestamp(String.valueOf(System.currentTimeMillis()));
String sign = generateCCBSign(request);
request.setSign(sign);
String response = callCCBQueryApi(request);
return parseCCBStatus(response);
}
@Override
public RefundResult refund(RefundRequest refundRequest) {
System.out.println("使用建设银行策略进行退款");
// 建设银行特有的退款逻辑
CCBRefundRequest request = new CCBRefundRequest(refundRequest);
request.setRefundType("CCB_SPECIAL_REFUND");
String response = callCCBRefundApi(request);
return parseCCBRefundResponse(response);
}
@Override
public String getBankCode() {
return BANK_CODE;
}
// 建设银行特有方法
private String encryptWithCCB(PaymentRequest request) {
// 建设银行特有的加密算法
return "CCB_ENCRYPTED_" + request.getOrderNo();
}
private Map<String, String> buildCCBHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("CCB-Api-Key", "your_ccb_api_key");
headers.put("CCB-Version", "2.0");
headers.put("Content-Type", "application/json");
return headers;
}
private String callCCBApi(String encryptedData, Map<String, String> headers) {
// 调用建设银行API
System.out.println("调用建设银行支付接口,headers: " + headers);
return "{\"return_code\":\"SUCCESS\",\"ccb_order_id\":\"CCB" + System.currentTimeMillis() + "\"}";
}
private PaymentResult parseCCBResponse(String response) {
// 解析建设银行响应
System.out.println("解析建设银行响应: " + response);
return PaymentResult.success("CCB" + System.currentTimeMillis());
}
// 其他建设银行特有方法...
}
农业银行策略
点击查看代码
/**
* 农业银行支付策略实现
*/
@Component("ABCPaymentStrategy")
public class ABCPaymentStrategy implements BankPaymentStrategy {
private static final String BANK_CODE = "ABC";
@Override
public PaymentResult pay(PaymentRequest paymentRequest) {
// 农业银行特有的支付逻辑
System.out.println("使用农业银行支付策略进行支付");
// 1. 农业银行需要特殊的参数转换
ABCPaymentRequest abcRequest = convertToABCRequest(paymentRequest);
// 2. 农业银行的签名方式不同
abcRequest.setSignature(generateABCSignature(abcRequest));
// 3. 调用农业银行接口(可能需要SSL双向认证)
String response = callABCApiWithSSL(abcRequest);
// 4. 农业银行特有的响应处理
return parseABCResponse(response);
}
@Override
public PaymentStatus queryStatus(String orderNo) {
System.out.println("使用农业银行策略查询订单状态");
// 农业银行特有的查询逻辑
ABCStatusRequest request = new ABCStatusRequest();
request.setOrderNo(orderNo);
request.setQueryType("FULL");
String response = callABCQueryApi(request);
return parseABCStatus(response);
}
@Override
public RefundResult refund(RefundRequest refundRequest) {
System.out.println("使用农业银行策略进行退款");
// 农业银行特有的退款逻辑
ABCRefundRequest request = new ABCRefundRequest(refundRequest);
request.setChannel("ABC_MOBILE");
String response = callABCRefundApi(request);
return parseABCRefundResponse(response);
}
@Override
public String getBankCode() {
return BANK_CODE;
}
// 农业银行特有方法
private ABCPaymentRequest convertToABCRequest(PaymentRequest request) {
ABCPaymentRequest abcRequest = new ABCPaymentRequest();
abcRequest.setTransAmount(request.getAmount());
abcRequest.setMerOrderNo(request.getOrderNo());
abcRequest.setCurrency("CNY");
abcRequest.setGoodsDesc(request.getGoodsName());
return abcRequest;
}
private String generateABCSignature(ABCPaymentRequest request) {
// 农业银行特有的签名算法
return "ABC_SIGNATURE_" + request.getMerOrderNo();
}
private String callABCApiWithSSL(ABCPaymentRequest request) {
// 调用农业银行API(需要SSL证书)
System.out.println("调用农业银行支付接口(SSL双向认证)");
return "{\"retCode\":\"000000\",\"retMsg\":\"成功\",\"abcOrderId\":\"ABC" + System.currentTimeMillis() + "\"}";
}
private PaymentResult parseABCResponse(String response) {
// 解析农业银行响应
System.out.println("解析农业银行响应: " + response);
return PaymentResult.success("ABC" + System.currentTimeMillis());
}
}
3. 策略上下文(支付服务)
/**
* 支付服务上下文
* 负责管理不同的银行支付策略
*/
@Service
public class BankPaymentStrategyRoute {
private final Map<String, BankPaymentStrategy> strategyMap = new ConcurrentHashMap<>();
/**
* 构造函数注入所有策略实现
*/
@Autowired
public BankPaymentStrategyRoute(List<BankPaymentStrategy> strategies) {
for (BankPaymentStrategy strategy : strategies) {
strategyMap.put(strategy.getBankCode(), strategy);
System.out.println("注册银行支付策略: " + strategy.getBankCode());
}
}
/**
* 支付方法
* @param bankCode 银行编码
* @param request 支付请求
* @return 支付结果
*/
public PaymentResult pay(String bankCode, PaymentRequest request) {
BankPaymentStrategy strategy = getStrategy(bankCode);
if (strategy == null) {
throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
}
// 验证支付参数
validatePaymentRequest(request);
// 执行支付策略
return strategy.pay(request);
}
/**
* 查询支付状态
* @param bankCode 银行编码
* @param orderNo 订单号
* @return 支付状态
*/
public PaymentStatus queryStatus(String bankCode, String orderNo) {
BankPaymentStrategy strategy = getStrategy(bankCode);
if (strategy == null) {
throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
}
return strategy.queryStatus(orderNo);
}
/**
* 退款
* @param bankCode 银行编码
* @param refundRequest 退款请求
* @return 退款结果
*/
public RefundResult refund(String bankCode, RefundRequest refundRequest) {
BankPaymentStrategy strategy = getStrategy(bankCode);
if (strategy == null) {
throw new IllegalArgumentException("不支持的银行编码: " + bankCode);
}
return strategy.refund(refundRequest);
}
/**
* 获取支付策略
* @param bankCode 银行编码
* @return 策略实例
*/
private BankPaymentStrategy getStrategy(String bankCode) {
return strategyMap.get(bankCode);
}
/**
* 获取支持的所有银行
* @return 银行编码列表
*/
public List<String> getSupportedBanks() {
return new ArrayList<>(strategyMap.keySet());
}
/**
* 动态注册策略(支持热更新)
* @param strategy 策略实例
*/
public void registerStrategy(BankPaymentStrategy strategy) {
strategyMap.put(strategy.getBankCode(), strategy);
System.out.println("动态注册银行策略: " + strategy.getBankCode());
}
/**
* 验证支付请求
*/
private void validatePaymentRequest(PaymentRequest request) {
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("支付金额必须大于0");
}
if (StringUtils.isBlank(request.getOrderNo())) {
throw new IllegalArgumentException("订单号不能为空");
}
// 更多验证...
}
}
4. 相关实体类
/**
* 支付请求参数
*/
@Data
public class PaymentRequest {
private String orderNo; // 订单号
private BigDecimal amount; // 金额
private String goodsName; // 商品名称
private String buyerId; // 买家ID
private String bankCardNo; // 银行卡号
private String mobile; // 手机号
// 其他通用参数...
}
/**
* 支付结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentResult {
private boolean success; // 是否成功
private String message; // 消息
private String bankOrderNo; // 银行订单号
private String payUrl; // 支付链接(如果需要跳转)
public static PaymentResult success(String bankOrderNo) {
return new PaymentResult(true, "支付成功", bankOrderNo, null);
}
public static PaymentResult fail(String message) {
return new PaymentResult(false, message, null, null);
}
}
/**
* 支付状态
*/
@Data
public class PaymentStatus {
private String orderNo; // 订单号
private String status; // 状态:SUCCESS/FAIL/PROCESSING
private String bankStatus; // 银行原始状态
private Date paymentTime; // 支付时间
}
/**
* 退款请求
*/
@Data
public class RefundRequest {
private String orderNo; // 原订单号
private String refundNo; // 退款单号
private BigDecimal amount; // 退款金额
private String reason; // 退款原因
}
/**
* 退款结果
*/
@Data
public class RefundResult {
private boolean success; // 是否成功
private String message; // 消息
private String refundId; // 银行退款ID
}
6. 使用示例
@SpringBootTest
public class BankPaymentTest {
@Autowired
private BankPaymentStrategyRoute route;
@Test
public void testBankPayment() {
// 模拟支付请求
PaymentRequest request = new PaymentRequest();
request.setOrderNo("ORDER20230215001");
request.setAmount(new BigDecimal("100.00"));
request.setGoodsName("测试商品");
request.setBuyerId("USER001");
// 测试工商银行支付
System.out.println("=== 测试工商银行支付 ===");
PaymentResult icbcResult = route.pay("ICBC", request);
System.out.println("工商银行支付结果: " + icbcResult);
// 测试建设银行支付
System.out.println("\n=== 测试建设银行支付 ===");
PaymentResult ccbResult = route.pay("CCB", request);
System.out.println("建设银行支付结果: " + ccbResult);
// 获取支持的所有银行
System.out.println("\n=== 支持的银行列表 ===");
List<String> banks = BankPaymentStrategyRoute.getSupportedBanks();
System.out.println("支持银行: " + banks);
}
}
📊 策略模式的优势
| 优势 | 说明 |
|---|---|
| 开闭原则 | 新增银行时,只需添加新策略类,无需修改现有代码 |
| 单一职责 | 每个银行策略只负责自己的业务逻辑 |
| 易于扩展 | 新增支付方式(如支付宝、微信)同样适用 |
| 便于测试 | 可以单独测试每个银行的策略 |
| 动态切换 | 运行时可根据银行编码动态切换策略 |
🔧 附:策略模式在Spring中的增强
使用工厂模式进一步增强
在上面的基础策略模式实现中,BankPaymentStrategyRoute(路由器/上下文)承担了两个核心职责:
- 策略的管理与路由:维护策略映射表,并根据bankCode选择正确的策略。
- 策略的执行:调用所选策略的业务方法(如pay)。
“工厂增强”提议将第一个职责中的“策略对象的获取/创建”部分剥离出来,交给一个专门的BankStrategyFactory(工厂)来处理。通过引入工厂模式,可以进一步解耦和精细化系统中各组件的职责。(当然,通常情况下,完全可以让 BankPaymentStrategyRoute 承担这2个职责,可以不必单拎出来一个 BankStrategyFactory 工厂类,这样更简洁)
/**
* 银行策略工厂
*/
@Component
public class BankStrategyFactory {
private final Map<String, BankPaymentStrategy> strategyMap = new HashMap<>();
@Autowired
public BankStrategyFactory(List<BankPaymentStrategy> strategies) {
for (BankPaymentStrategy strategy : strategies) {
strategyMap.put(strategy.getBankCode().toLowerCase(), strategy);
}
}
/**
* 根据银行编码获取策略
*/
public BankPaymentStrategy getStrategy(String bankCode) {
BankPaymentStrategy strategy = strategyMap.get(bankCode.toLowerCase());
if (strategy == null) {
throw new UnsupportedOperationException("不支持的银行: " + bankCode);
}
return strategy;
}
/**
* 判断是否支持该银行
*/
public boolean supports(String bankCode) {
return strategyMap.containsKey(bankCode.toLowerCase());
}
}
借助 annotation注解 来进行自动注册
上文使用策略接口BankPaymentStrategy中的 getBankCode 来定义策略。
本章节通过自定义注解 @BankStrategy 来定义策略bean。先上代码。
/**
* 银行策略注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface BankStrategy {
String value(); // 银行编码
}
/**
* 带注解的策略实现
*/
@BankStrategy("icbc")
public class ICBCPaymentStrategy implements BankPaymentStrategy {
// 实现...
}
/**
* 使用注解扫描的策略工厂
*/
@Component
public class BankStrategyFactory {
private final Map<String, BankPaymentStrategy> strategyMap = new HashMap<>();
@Autowired
public BankStrategyFactory(ApplicationContext context) {
Map<String, Object> beans = context.getBeansWithAnnotation(BankStrategy.class);
beans.forEach((beanName, bean) -> {
if (bean instanceof BankPaymentStrategy) {
BankStrategy annotation = bean.getClass().getAnnotation(BankStrategy.class);
strategyMap.put(annotation.value(), (BankPaymentStrategy) bean);
}
});
}
public BankPaymentStrategy getStrategy(String bankCode) {
return strategyMap.get(bankCode);
}
}
与基础实现方式的对比
-
基础实现方式:路由器(BankPaymentStrategyRoute)在其构造函数中,通过@Autowired List
获取到所有实现了BankPaymentStrategy接口的Bean,然后遍历这个列表,调用每个Bean的getBankCode()方法,将得到的编码和Bean本身放入Map。 -
注解自动注册方式:专门的工厂(BankStrategyFactory)利用Spring的ApplicationContext,只获取带有@BankStrategy注解的Bean,并读取注解中的value值(即"icbc")作为标识,构建Map。
代码结构建议:
使用该策略模式的建议的代码分层及层级结构如下:
bankpayment
├── model # 核心模型(公共领域对象)
│ ├── PaymentRequest.java
│ ├── PaymentResult.java
│ ├── PaymentStatus.java
│ └── RefundRequest.java
├── common # 公共基础设施
│ ├── BankPaymentException.java
│ ├── UnsupportedBankException.java
│ └── BankConstants.java
├── strategy # 策略模式核心
│ ├── BankPaymentStrategy.java
│ ├── BankPaymentStrategyRoute.java
│ └── BankPaymentFactory.java
├── icbc # 工商银行实现
│ ├── ICBCPaymentStrategy.java
│ ├── model
│ │ ├── ICBCPaymentRequest.java
│ │ └── ICBCPaymentResponse.java
│ ├── client
│ │ └── ICBCClient.java
│ └── util
│ └── ICBCSignUtil.java
└── ccb # 建设银行实现
├── CCBPaymentStrategy.java
├── model
├── client
└── util
结构说明与优势:
-
model(根包下):包含了系统最核心、与所有银行支付流程相关的领域模型。将其置于顶层,使得strategy、icbc、ccb等所有子模块都能无歧义、直接地引用这些公共请求/响应对象,体现了它们在领域中的中心地位。 -
common:现在专注于存放支撑系统运行的、与技术实现相关的公共部分。
(common包没有再分exception、constant、enums等子包,结构更加扁平) -
strategy:策略模式的核心定义(接口、路由器、工厂)保持不变,它们依赖并使用顶层的model。 -
icbc/ccb:各家银行的具体实现。其内部的model子包用于定义该银行特有的请求/响应对象;client用于封装与该银行API的通信;util存放该银行特有的工具(如签名算法)。
此结构结构简洁、清晰。“清晰”指的是————包的职责更加清晰:
- 顶层
model:定义做什么(业务领域)。 common:定义如何支撑(技术基础设施)。strategy:定义如何组织(设计模式与流程)。- 银行子包:定义如何实现(具体银行的业务逻辑)。
连AI也点评:这是一个兼顾了领域驱动设计(DDD)与清晰技术分层的包结构,具有良好的可读性和可维护性。
🎯 总结
通过策略模式,我们成功地将不同银行的支付逻辑解耦。正如本文的示例代码中:
- 工商银行:使用特定的签名算法和通信方式
- 建设银行:使用特殊的加密方式和请求头
- 农业银行:需要SSL双向认证
当需要对接新的银行或三方支付(如支付宝)时,只需要:
- 创建
AlipayPaymentStrategy实现BankPaymentStrategy - 实现该银行的特定逻辑
- 自动被Spring扫描并注册到策略工厂
这样的设计使得系统具备了良好的扩展性和维护性,完全符合开闭原则。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/19654563
浙公网安备 33010602011771号