侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 85 篇文章
  • 累计创建 40 个标签
  • 累计收到 0 条评论

设计模式之责任链模式

qingtian
2020-12-21 / 0 评论 / 0 点赞 / 844 阅读 / 12,446 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2020-12-31,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

设计模式之责任链模式

来源自《重学Java设计模式》链接提取码:ytc3

责任链模式

image-20201214143302191

责任链的核心是解决一组服务中的先后执行处理关系。

案例场景模拟

image-20201218135600160

在本案例中我们模拟在618⼤促期间的业务系统上线审批流程场景

像是这些⼀线电商类的互联⽹公司,阿⾥、京东、拼多多等,在618期间都会做⼀些运营活动场景以及提供的扩容备战,就像过年期间百度的红包⼀样。但是所有开发的这些系统都需要陆续的上线,因为临近618有时候也有⼀些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批⼒度。就像⼀级响应、⼆级响应⼀样。⽽这审批的过程在随着特定时间点会增加不同级别的负责⼈加⼊,每个⼈就像责任链模式中的每⼀个核⼼点。对于研发⼩伙伴并不需要关⼼具体的审批流程处理细节,只需要知道这个上线更严格,级别也更⾼,但对于研发⼈员来说同样是点击相同的提审按钮,等待审核。

接下来我们就模拟这样⼀个业务诉求场景,使⽤责任链的设计模式来实现此功能。

场景简述

模拟审核服务

public class AuthService {

    private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();

    public static Date queryAuthInfo(String uId, String orderId) {
        return authMap.get(uId.concat(orderId));
    }

    public static void auth(String uId, String orderId) {
        authMap.put(uId.concat(orderId), new Date());
    }

}
  • 这⾥⾯提供了两个接⼝⼀个是查询审核结果( queryAuthInfo )、另外⼀个是处理审核( auth )。
  • 这部分是把由谁审核的和审核的单⼦ID作为唯⼀key值记录到内存Map结构中。

常规实现

按照我们的需求审批流程,平常系统上线只需要三级负责⼈审批就可以,但是到了618⼤促时间点,就需要由⼆级负责以及⼀级负责⼈⼀起加⼊审批系统上线流程。在这⾥我们使⽤⾮常直接的if判断⽅式来实现这样的需求

代码实现

public class AuthController {

    private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化

    public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {

        // 三级审批
        Date date = AuthService.queryAuthInfo("1000013", orderId);
        if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", "王工");

        // 二级审批
        if (authDate.after(f.parse("2020-06-01 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000012", orderId);
            if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", "张经理");
        }

        // 一级审批
        if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000011", orderId);
            if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", "段总");
        }

        return new AuthInfo("0001", "单号:", orderId, " 状态:审批完成");
    }

}
  • 这里从上到下分别指定了在指定时间内有不同的人员进行审批,就像618上线的时候需要三个负责人都审批才能让系统进行上线。
  • 像是这样的功能看起来很简单的,但是实际的业务中会有很多部门,但如果这样实现就很难进行扩展,而且在改动扩展调整也非常麻烦。

测试验证

测试类

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_AuthController() throws ParseException {
        AuthController authController = new AuthController();

        // 模拟三级负责人审批
        logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
        logger.info("测试结果:{}", "模拟三级负责人审批,王工");
        AuthService.auth("1000013", "1000998004813441");

        // 模拟二级负责人审批
        logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
        logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
        AuthService.auth("1000012", "1000998004813441");

        // 模拟一级负责人审批
        logger.info("测试结果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
        logger.info("测试结果:{}", "模拟一级负责人审批,段总");
        AuthService.auth("1000011", "1000998004813441");

        logger.info("测试结果:{}", "审批完成");
    }

}

测试结果

09:42:41.584 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
09:42:41.603 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟三级负责人审批,王工
09:42:41.613 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:审批完成"}
09:42:41.613 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟二级负责人审批,张经理
09:42:41.613 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:审批完成"}
09:42:41.613 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟一级负责人审批,段总
09:42:41.613 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:审批完成

  • 这里模拟每次查询是否审批完成,随着审批的不同节点,之后继续由不同的负责人进行审批操作。
  • authController.doAuth,是查看审批的流程节点,AuthService.auth,是审批方法用于操作节点的流程状态。

使用责任链模式

责任链模式可以让各个服务模块更加清晰,⽽每⼀个模块间可以通过 next 的⽅式进⾏获取。⽽每⼀个 next 是由继承的统⼀抽象类实现的。最终所有类的职责可以动态的进⾏编排使⽤,编排的过程可以做成可配置化。

image-20201219164222582

  • 上图是这个业务模型中责任链结构的核心部分,通过三个实现了统一抽象类AuthLink的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。
  • 一般在使用责任链模式的时候如果场景比较固定,可以通过写死在代码中进行初始化,但如果业务场景经常变化可以做成xml配置的方式进行处理,也可以在库里进行初始化操作。

代码实现

责任链中返回对象定义

ublic class AuthInfo {

    private String code;
    private String info = "";

    public AuthInfo(String code, String ...infos) {
        this.code = code;
        for (String str:infos){
            this.info = this.info.concat(str);
        }
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
  • 这个类是包装了责任链处理过程中返回结果的类,方便处理每个责任链的返回信息。

链路抽象类定义

public abstract class AuthLink {

    protected Logger logger = LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
    protected String levelUserId;                           // 级别人员ID
    protected String levelUserName;                         // 级别人员姓名
    private AuthLink next;                                  // 责任链

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);

}
  • 这部分是责任链,链接起来的核心部分。AuthLink next,重点在于可以通过next方式获取下一个链路需要处理的节点。
  • levelUserIdlevelUserName,是责任链中的公用信息,标记每一个审核节点的人员信息。
  • 抽象类中定义了一个抽象方法,abstract AuthInfo doAuth,这是每一个实现者必须实现的方法,不同的审核级别处理不同的业务。

三个审核实现类

Level1AuthLink

public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

Level2AuthLink

public class Level2AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-11 00:00:00");
    private Date endDate = f.parse("2020-06-20 23:59:59");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

Level3AuthLink

public class Level3AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-01 00:00:00");
    private Date endDate = f.parse("2020-06-25 23:59:59");

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}
  • 如上三个审核实现类:Level1AuthLink,Level2AuthLink,Level3AuthLink,实现了不同级别的审核处理的简单逻辑。
  • 例如第一个审核类中会先判断是否审核通过,如果没有审核通过则返回结果给客户端,引导去审核。
  • 判断完成后获取下一个审核节点:super.next();如果不存在下一个节点,则直接返回结果。
  • 之后根据不同的业务时间段进行判断是否需要二级和三级审核。
  • 最后返回下⼀个审核结果;next.doAuth(uId, orderId, authDate);,有点像递归调⽤。

测试验证

测试类
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_AuthLink() throws ParseException {
        AuthLink authLink = new Level3AuthLink("1000013", "王工")
                .appendNext(new Level2AuthLink("1000012", "张经理")
                        .appendNext(new Level1AuthLink("1000011", "段总")));

        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

        // 模拟三级负责人审批
        AuthService.auth("1000013", "1000998004813441");
        logger.info("测试结果:{}", "模拟三级负责人审批,王工");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

        // 模拟二级负责人审批
        AuthService.auth("1000012", "1000998004813441");
        logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

        // 模拟一级负责人审批
        AuthService.auth("1000011", "1000998004813441");
        logger.info("测试结果:{}", "模拟一级负责人审批,段总");
        logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    }

}
  • 这里包括最核心的责任链的创建,实际业务中会包装到控制层:AuthLink authLink = new Level3AuthLink("1000013", "王工") .appendNext(new Level2AuthLink("1000012", "张经理") .appendNext(new Level1AuthLink("1000011", "段总")));通过把不同的责任节点进行组装,构造一条完整业务的责任链。
  • 接下来不断的执行查看审核链路authLink.doAuth(...),通过返回结果对数据进行3、2、1级负责人审核,直至最后审核全部完成。
测试结果
21:45:52.891 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
21:45:52.901 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟三级负责人审批,王工
21:45:52.901 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待二级审批负责人 张经理"}
21:45:52.901 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟二级负责人审批,张经理
21:45:52.901 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待一级审批负责人 段总"}
21:45:52.902 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:模拟一级负责人审批,段总
21:45:52.902 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0000","info":"单号:1000998004813441 状态:一级审批完成负责人 时间:2020-12-19 21:45:52 审批人:段总"}

Process finished with exit code 0
  • 从上述的结果可以看到我们的责任链已经生效,按照责任链的结果一次次审批,直至最后输出审批结果。
  • 这样的责任链的设计方式可以方便的进行扩展和维护,也把if语句干掉了。

总结

  • 从上⾯代码从if语句重构到使⽤责任链模式开发可以看到,我们的代码结构变得清晰干净了,也解

    决了大量if语句的使用。并不是if语句不好,只不过if语句并不适合做系统流程设计,但是在做判断

    和⾏为逻辑处理中还是⾮常可以使⽤的。

  • 在我们前⾯学习结构性模式中讲到过组合模式,它像是⼀颗组合树⼀样,我们搭建出⼀个流程决策

    树。其实这样的模式也是可以和责任链模型进⾏组合扩展使⽤,⽽这部分的᯿点在于如何关联链路

    的关联,最终的执⾏都是在执⾏在中间的关系链。

  • 责任链模式很好的处理单⼀职责和开闭原则,简单了耦合也使对象关系更加清晰,⽽且外部的调⽤

    ⽅并不需要关⼼责任链是如何进⾏处理的*(*以上程序中可以把责任链的组合进⾏包装,在提供给外

    部使⽤*)*。但除了这些优点外也需要是适当的场景才进⾏使⽤,避免造成性能以及编排混乱调试测

    试疏漏问题。

0

评论区