重构聚合支付案例教你如何写出高扩展性易读的代码
前言
人间清醒
聚合支付历史版本代码
以下代码逻辑为:按照不同的支付方式调用不同支付方式的逻辑流程。
痛点:
- 增加一种支付方式就要加入一个case,违反了开闭原则
- 代码累计在一个类中日积月累越来越沉重,可读性极差
- 增加一种支付方式就需要在原来的代码上动刀,扩展性极低
/**
* 旧的支付
*
* @param mode 模式
* @param payVO 支付签证官
* @return {@link String}
*/
@PostMapping("/old/{mode}")
public String oldPay(@PathVariable("mode") String mode, @RequestBody PayVO payVO) {
switch (mode) {
case "ALIPAY":
// 调用支付宝支付流程逻辑方法
break;
case "WXPAY":
// 调用微信支付流程逻辑方法
break;
case "EBANK":
// 调用E支付流程逻辑方法
break;
default:
return "支付方式未找到!";
}
return "支付成功!";
}
看逻辑和你写的代码八九不离十吧?
当时你的想法可能是“能实现这个需求就行,扩展性是啥?可维护性是啥?关我鸟事”,于是劈里啪啦一千又一千行造就了一个万行类诞生,根据需求的变更,需求的迭代慢慢的发现自己都改不动万行类,这时候咋办?(-v-,程序和人一个能跑就行-。-);
重构版本聚合支付代码
考虑到大部分项目使用了Spring,那咋们今天就用Spring的特性来实现这次重构的需求。
定义支付统一参数DTO
这里DTO定义两个参数:
- mode: 支付方式,如:支付宝,微信等等;
- T: 泛型,定义为泛型主要考虑到不同第三方平台的支付接口参数不一样,保证比较好的适配。
/**
* 支付dto
*
* @author wentao.wu
* @date 2022/01/05
*/
@Data
public class PayDTO<T> implements Serializable {
/**
* 模式
*/
private String mode;
/**
* 数据
*/
private T data;
}
定义支付行为
这里定义好一个支付行为需要做的事情,方法作用解读:
- befor: 之前需要执行的操作写着这里面,比如需要进行payDTO参数转换,校验支付行为是否合法等等。
- invoke: 执行具体的支付逻辑,比如调用微信支付接口进行支付。
- errorAfter:invoke方法执行失败后调用,比如支付失败记录失败日志等。
- okAfter:invoke方法执行成功后调用,比如支付成功后发送消息通知通知用户支付成功等。
/**
* 支付服务
*
* @author wentao.wu
* @date 2022/01/04
*/
public interface PayService {
/**
* 支付之前
*
* @param payDTO 支付dto
*/
<T> void befor(PayDTO<T> payDTO);
/**
* 执行支付
*
* @param payDTO 支付dto
* @return boolean
*/
<T> boolean invoke(PayDTO<T> payDTO);
/**
* 支付失败后
*
* @param payDTO 支付dto
*/
<T> void errorAfter(PayDTO<T> payDTO);
/**
* 支付成功后
*
* @param payDTO 支付dto
*/
<T> void okAfter(PayDTO<T> payDTO);
}
定义支付方式注解
这里定义注解主要是声明PayService实现类对应了什么方式,用来标注在具体的支付方式实现类中。
/**
* 支付
*
* @author wentao.wu
* @date 2022/01/05
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Pay {
/**
* 模式: WXPAY ,ALIPAY.....
*
* @return {@link String}
*/
String mode();
}
定义支付具体逻辑Bean保存容器
这里主要是保存所有PayService的实现类,后续会通过前端参数传入的支付方式从容器中寻找系统是否支持该支付方式。
/**
* 支付bean容器
*
* @author wentao.wu
* @date 2022/01/05
*/
public class PayBeanContainer {
/**
* bean管理器
*/
public Map<String, PayService> CONTAINER = new HashMap<>(16);
/**
* 添加bean
*
* @param mode 支付方式
* @param bean Bean
*/
public void addBean(String mode, PayService bean) {
if (checkBean(mode)) {
throw new RuntimeException("已存在该业务规则!");
}
CONTAINER.put(mode, bean);
}
/**
* 获取Bean
*
* @param mode 支付方式
* @return {@link PayService}
*/
public PayService getBean(String mode) {
if (!checkBean(mode)) {
throw new RuntimeException("不存在该Bean!");
}
return CONTAINER.get(mode);
}
/**
* 检查Bean是否存在
*
* @param mode 支付方式
* @return boolean
*/
public boolean checkBean(String mode) {
return CONTAINER.containsKey(mode);
}
}
PayService实现注册PayBeanContainer容器中
这里通过Spring初始化Bean后将所有PayService的实现类注册到支付容器中,支付方式则对应Pay注解中的mode。
/**
* 支付bean注册表
*
* @author wentao.wu
* @date 2022/01/04
*/
@Slf4j
@Component
public class PayServiceInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Autowired
private PayBeanContainer payBeanContainer;
/**
* 发布过程实例化后
*
* @param bean 豆
* @param beanName bean的名字
* @return boolean
* @throws BeansException 豆子例外
*/
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (bean instanceof PayService) {
Pay annotation = bean.getClass().getAnnotation(Pay.class);
payBeanContainer.addBean(annotation.mode(), (PayService) bean);
log.info(" PayBeanContainer.addBean:{}", bean.getClass().getName());
}
return true;
}
}
定义支付模板方法
统一支付流程的规范,并且实现调用的流程,具体子流程由开发自己实现。
当前模板方法主要统一了支付调用流程:
- 判断支付方式是否存在,存在则向下走。
- 从容器获取该支付方式的具体逻辑实现类。
- 调用支付前置方法。
- 执行支付。
- 支付成功,调用支付成功回调方法。
- 支付失败,调用支付失败回调方法。
这里定义了大概流程,具体微信支付,支付前,支付,支付后回调需要处理什么逻辑由开发自行实现。
/**
* 支付模板
*
* @author wentao.wu
* @date 2022/01/04
*/
@Component
public class PayTemplate {
@Autowired
private PayBeanContainer payBeanContainer;
/**
* 支付
*
* @param payDTO 支付dto
* @return boolean
*/
public <T> boolean pay(PayDTO<T> payDTO) {
if (!payBeanContainer.checkBean(payDTO.getMode())) {
throw new RuntimeException("平台不支持该支付方式,非法调用!");
}
PayService server = payBeanContainer.getBean(payDTO.getMode());
server.befor(payDTO);
boolean result = server.invoke(payDTO);
if (result) {
server.okAfter(payDTO);
} else {
server.errorAfter(payDTO);
}
return result;
}
}
开发微信支付
经过我们上面的铺垫开发实现微信支付只需要统一实现PayService并且声明这个子类为微信支付的具体调用逻辑(@Pay(mode = "WXPAY"))
/**
* wxpay impl
*
* @author wentao.wu
* @date 2022/01/06
*/
@Component
@Slf4j
@Pay(mode = "WXPAY")
public class WXPayImpl implements PayService {
@Override
public <T> void befor(PayDTO<T> payDTO) {
log.info("微信支付前,准备参数等....");
}
@Override
public <T> boolean invoke(PayDTO<T> payDTO) {
log.info("调用微信支付接口提交支付....");
return true;
}
@Override
public <T> void errorAfter(PayDTO<T> payDTO) {
log.info("微信支付失败记录日志....");
}
@Override
public <T> void okAfter(PayDTO<T> payDTO) {
log.info("微信支付成功给用户发送消息通知用户支付成功了....");
}
}
提供统一支付接口给前端调用
创建前端参数VO:
/**
* 支付VO
*
* @author wentao.wu
* @date 2022/01/05
*/
@Data
public class PayVO implements Serializable {
private String param1;// xx支付需要的参数
}
提供接口给前端调用:
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private PayTemplate payTemplate;
/**
* 支付
*
* @param mode 模式
* @param payVO 支付签证官
* @return {@link String}
*/
@PostMapping("/{mode}")
public String pay(@PathVariable("mode") String mode, @RequestBody PayVO payVO) {
PayDTO<PayVO> payDTO = new PayDTO<>();
payDTO.setMode(mode);
boolean reslult = payTemplate.pay(payDTO);
if (reslult) {
return "支付成功!";
}
return "支付失败!";
}
/**
* 旧的支付
*
* @param mode 模式
* @param payVO 支付签证官
* @return {@link String}
*/
@PostMapping("/old/{mode}")
public String oldPay(@PathVariable("mode") String mode, @RequestBody PayVO payVO) {
switch (mode) {
case "ALIPAY":
// 调用支付宝支付流程逻辑方法
break;
case "WXPAY":
// 调用微信支付流程逻辑方法
break;
case "EBANK":
// 调用E支付流程逻辑方法
break;
default:
return "支付方式未找到!";
}
return "支付成功!";
}
}
调用统一支付接口测试微信支付
Postman调用:http://localhost:7776/pay/WXPAY
RequestBody参数为:{"param1":"参数1"}
调用结果:支付成功!
控制台打印日志:
2022-01-06 12:45:37.441 INFO 15276 --- [nio-7776-exec-3] c.g.b.g.service.impl.WXPayImpl : 微信支付前,准备参数等....
2022-01-06 12:45:37.441 INFO 15276 --- [nio-7776-exec-3] c.g.b.g.service.impl.WXPayImpl : 调用微信支付接口提交支付....
2022-01-06 12:45:37.442 INFO 15276 --- [nio-7776-exec-3] c.g.b.g.service.impl.WXPayImpl : 微信支付成功给用户发送消息通知用户支付成功了....
扩展支付宝支付
这里我们需要扩展一种新的支付方式不需要改动任何代码,遵守开闭原则,每个类单一职责。
/**
* 支付宝impl
*
* @author wentao.wu
* @date 2022/01/06
*/
@Component
@Slf4j
@Pay(mode = "ALIPAY")
public class AlipayImpl implements PayService {
@Override
public <T> void befor(PayDTO<T> payDTO) {
log.info("支付宝支付前,准备参数等....");
}
@Override
public <T> boolean invoke(PayDTO<T> payDTO) {
log.info("调用支付宝支付接口提交支付....");
return true;
}
@Override
public <T> void errorAfter(PayDTO<T> payDTO) {
log.info("支付宝支付失败记录日志....");
}
@Override
public <T> void okAfter(PayDTO<T> payDTO) {
log.info("支付宝支付成功给用户发送消息通知用户支付成功了....");
}
}
调用统一支付接口测试支付宝支付
Postman调用:http://localhost:7776/pay/ALIPAY
RequestBody参数为:{"param1":"参数1"}
调用结果:支付成功!
控制台打印日志:
2022-01-06 13:49:50.141 INFO 13656 --- [nio-7776-exec-2] c.g.b.g.service.impl.AlipayImpl : 支付宝支付前,准备参数等....
2022-01-06 13:49:50.141 INFO 13656 --- [nio-7776-exec-2] c.g.b.g.service.impl.AlipayImpl : 调用支付宝支付接口提交支付....
2022-01-06 13:49:50.141 INFO 13656 --- [nio-7776-exec-2] c.g.b.g.service.impl.AlipayImpl : 支付宝支付成功给用户发送消息通知用户支付成功了....
总结
源代码地址: https://gitee.com/SimpleWu/blogs-examples/tree/master/grace-pay-case
- 经过重构后代码可读性变得强了起来,需要修改微信支付看WXPayImpl,需要修改支付宝支付看AlipayImpl,让代码变得清晰。
- 经过重构后代码扩展性变得强了起来,需要扩展只需要继承PayService并且声明@Pay支付方式即可。
- 使用了模板方法模式,组成了支付流程骨架,将步骤延迟到了依赖类去实现。
- 使用了策略模式将所有支付方式统一保存管理了起来,当需要某种策略时则去向策略管理器要。
- ......
今天你学费了吗?
-.-