拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 重构聚合支付案例教你如何写出高扩展性易读的代码

重构聚合支付案例教你如何写出高扩展性易读的代码

白鹭 - 2022-03-07 2113 0 0

前言

人间清醒

目录
  • 前言
  • 聚合支付历史版本代码
  • 重构版本聚合支付代码
    • 定义支付统一自变量DTO
    • 定义支付行为
    • 定义支付方式注解
    • 定义支付具体逻辑Bean保存容器
    • PayService实作注册PayBeanContainer容器中
    • 定义支付模板方法
    • 开发微信支付
    • 提供统一支付界面给前端呼叫
    • 呼叫统一支付界面测验微信支付
    • 扩展支付宝支付
    • 呼叫统一支付界面测验支付宝支付
    • 总结
    • 今天你学费了吗?

聚合支付历史版本代码

以下代码逻辑为:按照不同的支付方式呼叫不同支付方式的逻辑流程,
痛点:

  1. 增加一种支付方式就要加入一个case,违反了开闭原则
  2. 代码累计在一个类中榷训月累越来越沉重,可读性极差
  3. 增加一种支付方式就需要在原来的代码上动刀,扩展性极低
 /**
     * 旧的支付
     *
     * @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定义两个自变量:

  1. mode: 支付方式,如:支付宝,微信等等;
  2. T: 泛型,定义为泛型主要考虑到不同第三方平台的支付界面自变量不一样,保证比较好的适配,
/**
 * 支付dto
 *
 * @author wentao.wu
 * @date 2022/01/05
 */
@Data
public class PayDTO<T> implements Serializable {
    /**
     * 模式
     */
    private String mode;
    /**
     * 资料
     */
    private T data;
}
定义支付行为

这里定义好一个支付行为需要做的事情,方法作用解读:

  1. befor: 之前需要执行的操作写着这里面,比如需要进行payDTO自变量转换,校验支付行为是否合法等等,
  2. invoke: 执行具体的支付逻辑,比如呼叫微信支付界面进行支付,
  3. errorAfter:invoke方法执行失败后呼叫,比如支付失败记录失败日志等,
  4. 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;
    }
}
定义支付模板方法

统一支付流程的规范,并且实作呼叫的流程,具体子流程由开发自己实作,
当前模板方法主要统一了支付呼叫流程:

  1. 判断支付方式是否存在,存在则向下走,
  2. 从容器获取该支付方式的具体逻辑实作类,
  3. 呼叫支付前置方法,
  4. 执行支付,
  5. 支付成功,呼叫支付成功回呼方法,
  6. 支付失败,呼叫支付失败回呼方法,

这里定义了大概流程,具体微信支付,支付前,支付,支付后回呼需要处理什么逻辑由开发自行实作,

/**
 * 支付模板
 *
 * @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

  1. 经过重构后代码可读性变得强了起来,需要修改微信支付看WXPayImpl,需要修改支付宝支付看AlipayImpl,让代码变得清晰,
  2. 经过重构后代码扩展性变得强了起来,需要扩展只需要继承PayService并且宣告@Pay支付方式即可,
  3. 使用了模板方法模式,组成了支付流程骨架,将步骤延迟到了依赖类去实作,
  4. 使用了策略模式将所有支付方式统一保存管理了起来,当需要某种策略时则去向策略管理器要,
  5. ......
今天你学费了吗?

-.-

学习是永无止境的,
标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *