设计模式组合拳,优化你的规则校验

1,682 阅读6分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 

前言

​ 在业务场景中,我们在针对用户还款的时候,需要对用户此次还款信息进行一系列的规则校验,诸如业务开关校验、还款在途校验、还款账户校验、产品业务规则校验等等,只有符合规则了,才允许你进行下一步的还款操作

​ 在当前我们的中台实现中,我们借助流程编排来实现校验环节,即我们会将一个个校验逻辑,拆成一个个节点来进行逻辑执行,简单一点理解,就是一个校验就是我们代码中的一个 bean

​ 上图,说明一下

p1 业务规则校验图.png

​ 为了保持我们流程的清爽以及减少项目现场实施同学的学习成本,决定将通用的业务规则校验进行整合,在流程编排中仅开放一个节点 “还款校验” 节点,如果项目有个性化的校验节点可自主在该校验节点前后进行添加即可

普通实现

​ 简单来说,我们要做的事情就是合并组件的事,将原先的四个组件合并成一个组件,那这不是手到擒来

HRseEq

​ 三下五除二,代码就实现完了

public Boolean checkRepayApply(CheckParam checkParam) {
    // 执行 A 规则校验,校验失败即走向还款失败流程
    if (!checkA(checkParam)) {
        return repayApplyFail();
    }
    // 执行 B 规则校验,校验失败即走向还款失败流程
    if (!checkB(checkParam)) {
        return repayApplyFail();
    }
    // 略
    if (!checkC(checkParam)) {
        return repayApplyFail();
    }
    // 略
    if (!checkD(checkParam)) {
        return repayApplyFail();
    }
    return Boolean.TRUE;
}

​ 代码完成 ✅

​ 提交测试 ✅

​ 下班 ✅

​ 但是这种实现逻辑,跑是能跑,但是假设我们的校验逻辑一旦增加,后续接手的张三同学、李四同学就会看着满屏的 if,然后打开历史提交记录,顺着提交记录把每个人都 tui 一遍

​ 为了 预防被骂,以及体现我们的技术水平,保持我们的代码对对于扩展开放,对于修改关闭,优雅我们的代码实现,我们需要针对这段代码进行重构,让这一段代码香起来

dhIoJx

优雅实现

​ 面对这种串联的业务执行,我们可以借助到我们的 “责任链” 设计模式,这种设计模式比较好理解,我们举个例子🌰

​ 有一天,你要请假去相亲,于是你在 OA 上提交了请假申请,需要经过小组组长、部门主管、部门经理,小组组长批假、部门主管批假、部门经理不批假,于是你相亲失败

0ti3XO

​ 责任链模式就是用于我们这种链条式的处理一个请求,对比我们还款中的校验是不是就可以完全套用了

​ 责任链模式的精髓在于它可以指定一个处理该请求的是那个执行器,那么我们的伪代码应该是这样的

AHandler aHandler = new AHandler();
BHandler bHandler = new BHandler();
CHandler cHandler = new CHandler();

aHandler.setNextRule(bHandler);
bHandler.setNextRule(cHandler);
aHandler.execute();

​ 看起来有点糟糕,这一段代码像不像我们在 new 出来一个对象的时候,不断进行 set 字段的时候,我们通常借助的是 lombok 去进行字段的 build,去简洁我们的代码,于是我们可以借助这种“build”思想,利用“建造者”这种设计模式,去把我们的执行器一个个 build 出来

​ 想法基本已经有了,接下来我们开始写 bug 逻辑

统一接口

我们需要写一个接口,让现在以及未来的规则执行器都统一实现该接口,去实现固定的接口

public interface IRule{

    /**
     * 规则执行
     *
     * @param t 校验参数
     * @return 执行结果
     */
    CheckResultDto execute(CheckParam t);

    /**
     * 指定下一个执行器
     *
     * @param supplier 利用其来创建对象
     * @return 规则执行器
     */
    IRule nextRule(Supplier<IRule> supplier);
}

这边我们可以注意到我们指定下一个执行器是利用的 Supplier 接口,补充说明一句 Supplier 是 Java8 提供给我们的一个接口对象,最大的特点是懒加载,我们利用其来指定执行器也是为了去简洁我们的代码,当然在这边 nextRule 的入参直接为 IRule 对象也是可以的

规则实现

​ 我们先写一个 Rule1 执行器,去实现我们的的 IRule 接口

​ execute 方法是我们的具体业务执行逻辑,当我们此次规则执行通过并且下一个执行器不为空的时候我们就进行下一个执行器的业务逻辑,如果下一个执行器为空,我们就直接 retun 执行结果就好了

​ nextRule 方法是我们指定下一个执行器是什么

public class Rule1 implements IRule {

    private IRule nextRule;

    @Override
    public CheckResultDto execute(CheckParam t) {
        System.out.println("第一个执行器运行中");
        if (t.getCheckFlag() && (Objects.nonNull(nextRule))) {
            return nextRule.execute(t);
        }
        return CheckResultDto.builder().result("我是第一个执行器的输出").build();
    }

    @Override
    public IRule nextRule(Supplier<IRule> supplier) {
        this.nextRule = supplier.get();
        return nextRule;
    }
}

​ 其余的规则执行器我们仿造这个写就好了,这边就不开冗余说明了

测试方法

public class Test {

    public static void main(String[] args) {

        CheckParam checkParam = new CheckParam();
        checkParam.setCheckFlag(Boolean.TRUE);

        Rule1 rule1 = new Rule1();
        rule1.nextRule(Rule2::new).nextRule(Rule3::new);

        CheckResultDto execute = rule1.execute(checkParam);
        
        System.out.println(execute.toString());
    }
}

// 输出结果
第一个执行器运行中
第二个执行器运行中
第三个执行器运行中
CheckResultDto{result='我是第三个执行器的输出'}

总结

​ 至此,我们的重构已经结束,我们回顾一下大体的实现逻辑

​ step1:编写 IRule 接口,指定业务逻辑执行方法 execute ,下一个执行器 nextRule 方法

​ step2:实现 IRule 接口,编写具体业务逻辑

​ step3:调用规则进行执行校验

​ 整体步骤其实很简单,但是大大优化了我们代码的可读性以及拓展能力,可支持校验组件拆卸组装,每一段代码都是由每一小行进行实现的,逐步优化好每一段代码,才可以避免我们shi山的累积

​ 这一部分轮子直接搬出来就可使用,所有源代码地址均在:github.com/isysc1/Issu…

闲言碎语

​ 周五在我骑着小毛驴上班的时候

​ 突然捡到了一个手机,本着拾机不昧的原则,我在原地等了五分钟,发现失主还不给我打电话

​ 我就拿着手机去了公司,本着严谨的原则我准备报个警,将手机交给警察叔叔,警察肯定会接过我的手机,然后对我给我竖一个大拇指,嗯,不错

​ 刚准备打我人生第一通报警电话,结果失主打电话进来了,很着急的样子,然后我们沟通了地点,我准备把手机还给她

​ 和失主交流了一下,确认了手机解锁密码,失主说今天刚好还是她生日,还留了我手机号,要请我喝奶茶

​ 虽然一天过去了,还没加我微信,但是想想我捡到手机也不是图人家的回报啥的,今天肯定还是失主最难忘的一次生日了,又是日行一善的一天 over