Skip to content

Latest commit

 

History

History
364 lines (270 loc) · 14.4 KB

13. 设计模式第十三节-责任链模式.md

File metadata and controls

364 lines (270 loc) · 14.4 KB

设计模式第十三节

责任链模式

引言

老人们常说:“等你大了,你就知道场景和场地的重要性了。

联系射击🏹需要去靶场、滑雪⛷️需要去滑雪场、开车🏎️需要能上路实践。而编程开发除了能完成产品的功能流程,还需要保证系统的可靠性。就像同学们可以听到的一些监控指标:“QPSTPSTP99TP999可用率响应时长,......”

而这些指标的综合评估分数就是一个系统的健康值,但是如果同学们几乎很少听到这些专业的名词,也没接触过高并发场景,那么就很像模拟考考了一百分,但是正式考试考砸了。没有技术场景给我们进行训练,让我们不断的体会系统的脾气秉性,即便你有再多的想法都没法实现。所以,如果真的想学习一定要去一个有实操的场景,下水试试才能学会狗刨。

有一个哲理名言:“你的视野盲区有多大?

同样的一本书、同样的一条路、同样一座城,我们真的会以为生活就是这样的吗?我们真的没有选择吗?其实大多数的时候选项就是摆设,可能给我们多少次一样的机会我们都会做出一模一样的选项。这不是你选不选而是你的认知范围决定了你下一秒做的事情,另外的一个下一秒又决定了再下一个下一秒。就像管中窥豹一样,20%的面积在你视觉里都是黑色的,甚至就总是忽略看不到,而这看不到的20%就是生命中的时运!但,人可以学习,可以成长,可以脱胎换骨,可以努力付出,通过一次次的蜕变而看到剩下的20%!

工程师们经常说:“没有设计图纸你敢盖楼吗?

编程开发中最好的是什么?是设计!运用架构思维、经验心得、才华灵感,构建出最佳的系统。真正的研发会把自己写出来的代码当成艺术品来欣赏,你说这是一份工作,但在这些人眼里这不只是一份简单的工作,这就是工匠精神。就像可能时而你也会为自己因为一个完美的设计而豪迈万丈,为能上线一个扛得住每秒200万访问量的系统会精神焕发。这样的自豪感就是一次次垒砖一样垫高脚底,不断的把你的视野提高,让你能看到上层设计也能知晓根基建设。可以把控全局,也可以治理细节。这一份份知识的沉淀,来帮助你绘制出一张系统架构蓝图。

责任链模式 介绍

wUJpU.png

责任链模式的核心是解决一组服务中的先后执行处理关系,你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。

案例模拟

引言

wUEjZ.png

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

像是这些一线电商类的互联网公司,阿里、京东、拼多多等,在618期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近618有时候也有一些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。

而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线更严格,级别也更高,但对于研发人员来说同样是点击相同的提审按钮,等待审核。

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

场景模拟

- chain-responsibility-model-1
  - main
    - java
      - com.hcy
        - AuthService.java

场景简述

2.1 模拟审核服务

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 模拟审核服务
 * @author HCY
 * @since 2021/4/21 12:53 下午
*/
public class AuthService {
    private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();

    /**
     * 查询审核结果
     * @author HCY
     * @since 2021/4/21 12:57 下午
     * @param uId:
     * @param orderId:
     * @return java.util.Date
    */
    public static Date queryAuthInfo(String uId, String orderId) {
        return authMap.get(uId.concat(orderId));
    }

    /**
     * 处理审核
     * @author HCY
     * @since 2021/4/21 12:58 下午
     * @param uId:
     * @param orderId:
     * @return void
    */
    public static void auth(String uId, String orderId) {
        authMap.put(uId.concat(orderId), new Date());
    }
}

正常解决方案

工程目录

- chain-responsibility-model-2
  - src.main.java.com.hcy
    - AuthController

项目代码

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class AuthController {
    /**
     * 时间格式化
     * @author HCY
     * @since 2021/4/21 3:18 下午
    */
    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上线的时候需要三个负责人都审批才能让系统进行上线。

- 像是这样的功能看起来很简单的,但是实际的业务中会有很多部门,但如果这样实现就很难进行扩展,并且在改动扩展调整也非常麻烦。

责任链模式重构代码

工程项目

- chain-responsibility-model-3
  - src.main.java.com.hcy
    - cuisine
      - impl
        - Level1AuthLink.java
        - Level2AuthLink.java
        - Level3AuthLink.java
    - AuthInfo.java
    - AuthLink.java

模型结构

wti4y.png

- 上图是这个业务模型中责任链结构的核心部分,通过三个实现了统一抽象类AuthLink的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。

- 一般在使用责任链时候如果是场景比较固定,可以通过写死到代码中进行初始化。但如果业务场景经常变化可以做成xml配置的方式进行处理,也可以落到库里进行初始化操作。

代码需求

责任链中返回对象定义
public 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;
    }
}
链路抽象类定义
/**
 * 审核规定;
 * 1. 601-610 三级审批 + 二级审批
 * 2. 611-620 三级审批 + 二级审批 + 一级审批
 * 3. 其他时间 三级审批
 */
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);

}
审核的实现类
/**
 * 一级负责人
 */
public class Level1AuthLink extends AuthLink {

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

    @Override
    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);
    }

}
/**
 * 二级负责人
 */
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);
    }
    @Override
    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);
    }

}
/**
 * 三级负责人
 */
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);
    }
    @Override
    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);,有点像递归调用。

总结

- 从上面代码从if语句重构到使用责任链模式开发可以看到,我们的代码结构变得清晰干净了,也解决了大量if语句的使用。并不是if语句不好,只不过if语句并不适合做系统流程设计,但是在做判断和行为逻辑处理中还是非常可以使用的。

- 在我们前面学习结构性模式中讲到过组合模式,它像是一颗组合树一样,我们搭建出一个流程决策树。其实这样的模式也是可以和责任链模型进行组合扩展使用,而这部分的重点在于如何关联链路的关联,最终的执行都是在执行在中间的关系链。

- 责任链模式很好的处理单一职责和开闭原则,简单了耦合也使对象关系更加清晰,而且外部的调用方并不需要关心责任链是如何进行处理的(以上程序中可以把责任链的组合进行包装,在提供给外部使用)。但除了这些优点外也需要是适当的场景才进行使用,避免造成性能以及编排混乱调试测试疏漏问题。