引子
最近在做数字人民币的对接,除了完成三方底层接口的对接外,就是设计把原有系统中支付相关的代码优化掉,过程中有很多问题,于是得此文以记录.
存在问题
- 工程模块划分模糊,模块职能不清晰
- 原有的第三方支付对接散落各处,有的在业务层的订单模块,有的在应用层的某个模块.
- 关于对接第三方接口的流程:
- 首先,对接第三方服务,实际是为当前系统提供功能拓展,对接流程尽量是在基础服务层完成原生接口的对接;
- 其次,在业务层完成对接功能的重新定义,比如定义请求和响应对象的结构体,根据自身系统业务需求定义各种类型状态等字典项以及异常提示等,目的是屏蔽第三方接口特征,在系统内暴露结合业务考量和再设计的接口
- 模块循环依赖
- 在进行支付功能入口统一的时候,想把支付功能下沉到基础模块,因为有的对接是在上层模块实现的,所以引入依赖时出现模块循环依赖,即上层调用下层支付,现在下层又调用上层中的某些对接支付的功能.
- 牢骚: 如果按照规范设计对模块进行重新调整,动作太大,工作量也太大…问当时开发者的话,回答大概就是当时只有一种支付,没考虑那么多.之前的N个开发对接了N种方式的支付,话说就没有一个去考虑拓展和兼容,现在轮到我进行支付的聚合,嗯,给N个人擦屁屁…
- 这里循环依赖主要通过依赖倒置来进行解决,这也是下层调用上层服务时惯用的套路.大体思路是:首先依赖倒置就是用抽象来解耦,模块间不调用具体实现,而是提供接口或抽象类.比如这里,我在下层定义支付的抽象类,然后在上层进行支付的实现, 现在我在下层如果引用上层中的某个支付实现类会出现模块循环依赖,但如果我在下层通过抽象获取所有的实现类,然后根据条件调用指定的实现类去执行,就可以避免循环依赖的问题.
public abstract class Pay{
public abstract Object doPay();
}
class Main{
// 前提,所有的实现类都交由spring管理
Map map = applicationContext.getBeansOfType(Pay.class);
for(Pay item: map.values()){
item.doPay();
}
}
- 代码优化
目前代码存在几个经典的问题:
- 重复代码量巨大
- if else 分支导致方法体庞大,分支套分支,大概400多行
- 相同逻辑的代码散落多处
/**
* 业务梳理: 我这里聚合的是小程序中调取第三方支付的环节,不论是哪种支付方式,流* 程都可以简化成2个主要环节:
* 1. 创建系统内部支付订单(公共)
* 2. 调用第三方支付(定制)
* 看到这里,使用模版模式进行重新设计比较简单,模版类中实现公共逻辑,特殊逻辑交给子类自己实现.好处是避免重复代码,即业务改动时,只改一处或只在一处改.同时将不同的支付方式分开,条理更清晰,根据抽象类可以知道所有的支付实现,后续拓展也方便.伪代码如下:
*/
public abstract class Pay{
public final Object doPay(Object param){
// 创建内部支付
PrePayOrder order = createInternalPrePayOrder(param);
// 调用外部支付
createExternalPay(order, param);
}
private PrePayOrder createInternalPrePayOrder(Object param){
// biz logic
}
public abstract Object createExternalPay(PrePayOrder order, Object param);
}
public class AliPay extends Pay{
public Object createExternalPay(PrePayOrder order, Object param){
if(order.type == Cons.AliPay){
// invoke aliPay
}
}
}
public class WxPay extends Pay{
public Object createExternalPay(PrePayOrder order, Object param){
if(order.type == Cons.WxPay){
// invoke aliPay
}
}
}
上面代码存在问题,虽然会调用相应的外部支付方法,但会多次执行公共方法
createInternalPrePayOrder
,因为方法调用处我们使用的是遍历所有实现类.所以提供一个钩子方法让子类决定模板方法的执行逻辑,同时再完善一下模板方法的执行逻辑.完整设计如下:
public abstract class Pay{
// 模板方法
public final Object doPay(Object param){
if(shouldExecute(param)){
paramCheck(param);
paramProcessBeforeCreate(param);
PrePayOrder order = createInternalPrePayOrder(param);
paramProcessAfterCreate(param);
createExternalPay(order, param);
}
}
private PrePayOrder createInternalPrePayOrder(Object param){
// biz logic
}
// 根据支付类型判断是否执行模板方法,子类必须实现
public abstract boolean shouldExecute(Object param);
// 参数检查
public void paramCheck(Object param){
// default: do nothing
}
// 参数预处理
public void paramProcessBeforeCreate(Object param){
// default: do nothing
}
// 参数预处理
public void paramProcessAfterCreate(Object param){
// default: do nothing
}
// 调用第三方支付
public abstract Object createExternalPay(PrePayOrder order, Object param);
}
public class AliPay extends Pay{
public boolean shouldExecute(Object param){
return order.type == Cons.AliPay;
}
// 覆盖父类的参数处理
public void paramProcessBeforeCreate(Object param){
if(param.getOpenid == null) {
param.setOpenid(selectFromOtherService());
// other setting
}
}
public Object createExternalPay(PrePayOrder order, Object param){
// invoke aliPay
}
}
public class WxPay extends Pay{
public boolean shouldExecute(Object param){
return order.type == Cons.WxPay;
}
// 覆盖父类的参数后置处理
public void paramProcessAfterCreate(Object param){
// 假设该支付方式保存金额单位为: 分
param.setAmount(param.getAmount * 100);
// 其他特殊逻辑
}
public Object createExternalPay(PrePayOrder order, Object param){
// invoke WxPay
}
}
总结
代码优化耗时,耗力,担风险.我想起之前看过的一本关于旧系统改造的书中的一句话:如果代码每经过一个开发人员之手后都变得更易于拓展,更容易阅读,也更方便改造,那现在的软件行业该是什么样子?大家还会讨厌老系统吗?或许讨厌的不是老系统,而是遗留系统吧.