zoukankan      html  css  js  c++  java
  • 23种设计模式(六)-责任链设计模式

    说到责任链设计模式, 我们平时使用的也真是挺多的. 比如: 天天用的网关过滤器, 我们请假的审批流, 打游戏通关, 我们写代码常用的日志打印. 他们都使用了责任链设计模式.

    下面就来详细研究一下责任链设计模式

    一. 什么是责任链设计模式?

    官方定义:


    责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

    在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

    大白话:


    定义中提到的两个主体: 请求的发送者和请求的接收者. 用员工请假来举例. 请求发送者是员工, 请求接收者是主管们.
    「对请求的发送者和接收者进行解耦」: 意思就是员工发起请假申请和主管审批请假解耦.
    「为请求创建了一个接收者对象的链」: 意思是接收者有多个, 实现了多个接收者进行审批的链条.

    二. 责任链设计模式的使用场景

    • 网关过滤器: 一个url请求过来, 首先要校验url是否是合法的, 不合法过滤掉, 合法进入下一层校验; 是否是在黑名单中, 如果在过滤掉,不在进行下一层校验; 校验参数是否合规, 不合规过滤掉, 合规进入下一层校验, 等等.
    • 请假审批流: 请假天数小于3天, 直属领导审批即可; 天数大于3天,小于10天, 要部门主管审批; 天数大于10天要总经理审批
    • 游戏通关: 完成第一关, 并且分数>90, 才能进入第二关; 完成第二关, 分数>80, 才能进入第三关等等
    • 日志处理: 日志的级别从小到大分别是: dubug, info ,warn, error .
      • console控制台: 控制台接收debug级别的日志, 那么所有debug, info, warn, error日志内容都打印在console控制台中.
      • file文件: file接收info级别的日志. 那么info, warn, error级别的日志都会打印到file文件中, 但是debug日志不会打印
      • error文件: 只接收error级别的日志, 其他界别的日志都不接收.

    三. 责任链设计模式的实现思路

    下面以一个简单的案例[请假审批流]来介绍责任链的实现

    1. 需求:

    有一个员工小力, 他要请求. 公司规定, 请假3天以内, 直属领导就可以审批. 请假3-10天, 需要部门经理审批. 请假大于10天需要总经理审批.

    2. 通常实现方式

    这个审批流, 我们第一想法是使用if....else....来写.

    public void approve(Integer days) {
        if (days <= 3) {
            // 直属领导审批
        } else if (days > 3 && days <= 10) {
            // 部门经理审批
        } else if (days > 10) {
            // 总经理审批
        }
    }
    

    这样写确实可以实现。 但是他有几个缺点:

    1. 这个审批方法很长,一大段代码看起来并不美观。 这里看着代码很少,那是因为我没有具体实现审批逻辑, 当审批人很多的时候, if...else...也会很多,就会显得很臃肿了。
    2. 可扩展性差: 加入现在要在部门经理和总经理之间在家一个审批流。 我们要修改原来的代码,修改原来的代码,就有可能引入bug, 违背了开放-封闭原则。
    3. 违背单一职责原则:这个类承担了多个角色的多个责任,违背了单一职责原则。
    4. 不能跨级别审批:加入有一个特殊的人,他请假3天,也需要总经理审批,这个if...else....就没法实现了。

    既然可能增加多个审批人,我们可以考虑将具体的审批人做成审批者的子类,利用多态来实现。

    3. 责任链实现方式

    第一步: 小力请假, 定义一个请假实体类LeaveRequest。这就是请求的发出者

    @Data
    public class LeaveRequest {
        /**
         * 请假的人
         */
        private String name;
    
        /**
         * 请假的天数
         */
        private int days;
    
        public LeaveRequest() {
        }
    
        public LeaveRequest(String name, int days) {
            this.name = name;
            this.days = days;
        }
    }
    

    有两个属性, 谁请假(name), 请了几天(days).

    第二步: 抽象请假审批者

    /**
     * 抽象的请假处理类
     */
    @Data
    public abstract class LeaveHandler {
        /**
         * 处理人姓名
         */
        private String handlerName;
    
        /**
         * 下一个处理人
         */
        private LeaveHandler nextHandler;
    
        public void setNextHandler(LeaveHandler leaveHandler) {
            this.nextHandler = leaveHandler;
        }
    
        public LeaveHandler(String handlerName) {
            this.handlerName = handlerName;
        }
    
        /**
         * 具体的处理操作
         * @param leaveRequest
         * @return
         */
        public abstract boolean process(LeaveRequest leaveRequest);
    }
    

    这里定义了如下内容:

    1. 审批者姓名,
    2. 审批人要执行的操作process()方法。审批的内容是请假信息, 返回值是审批结果,通过或者不通过
    3. 下一个处理者nextHandler:这是重点。也是我们链条能够连续执行的关键。

    第三步:定义具体的操作者

    • 直属领导处理类:DirectLeaveHandler.java
    /**
     * 天数小于3天, 直属领导处理
     */
    public class DirectLeaveHandler extends LeaveHandler{
        public DirectLeaveHandler(String directName) {
            super(directName);
        }
        @Override
        public boolean process(LeaveRequest leaveRequest) {
            // 随机数大于3则为批准,否则不批准
            boolean result = (new Random().nextInt(10)) > 3;
            if (!result) {
                System.out.println(this.getHandlerName() + "审批驳回");
                return false;
            } else if (leaveRequest.getDays() <= 3) {
                // 审批通过
                System.out.println(this.getHandlerName() + "审批完成");
                return true;
            } else{
                System.out.println(this.getHandlerName() + "审批完成");
                return this.getNextHandler().process(leaveRequest);
            }
        }
    }
    

    这里模拟了领导审批的流程. 如果小于3天, 直属领导直接审批, 可能通过, 可能不通过. 如果超过3天, 提交给下一级领导审批.

    • 部门经理处理类: ManagerLeaveHandler
    public class ManagerLeaveHandler extends LeaveHandler{
    
        public ManagerLeaveHandler(String name) {
            super(name);
        }
        @Override
        public boolean process(LeaveRequest leaveRequest) {
            // 随机数大于3则为批准,否则不批准
            boolean result = (new Random().nextInt(10)) > 3;
            if (!result) {
                System.out.println(this.getHandlerName() + "审批驳回");
                return false;
            } else if (leaveRequest.getDays() > 3 && leaveRequest.getDays() <= 10) {
                System.out.println(this.getHandlerName() + "审批完成");
                return true;
            } else {
                System.out.println(this.getHandlerName() + "审批完成");
                return this.getNextHandler().process(leaveRequest);
            }
        }
    }
    

    部门经理处理的是3-10天的假期, 如果超过10天, 还要交由下一级领导审批
    ** 总经理处理类:

    public class GeneralManagerLeavHandler extends LeaveHandler{
        public GeneralManagerLeavHandler(String name) {
            super(name);
        }
        @Override
        public boolean process(LeaveRequest leaveRequest) {
            // 随机数大于3则为批准,否则不批准
            boolean result = (new Random().nextInt(10)) > 3;
            if (!result) {
                System.out.println(this.getHandlerName() + "审批驳回");
                return false;
            } else {
                System.out.println(this.getHandlerName() + "审批完成");
                return true;
            }
        }
    }
    

    左右最终流转到总经理的假期都会被审批

    第四步: 定义客户端发起请求操作

        public static void main(String[] args) {
            DirectLeaveHandler directLeaveHandler = new DirectLeaveHandler("直属主管");
            ManagerLeaveHandler managerLeaveHandler = new ManagerLeaveHandler("部门经理");
            GeneralManagerLeavHandler generalManagerLeavHandler = new GeneralManagerLeavHandler("总经理");
    
            directLeaveHandler.setNextHandler(managerLeaveHandler);
            managerLeaveHandler.setNextHandler(generalManagerLeavHandler);
    
            System.out.println("========张三请假2天==========");
            LeaveRequest lxl = new LeaveRequest("张三", 2);
            directLeaveHandler.process(lxl);
            
            System.out.println("========李四请假6天==========");
            LeaveRequest wangxiao = new LeaveRequest("李四", 6);
            directLeaveHandler.process(wangxiao);
    
    
            System.out.println("========王五请假30天==========");
            LeaveRequest yongMing = new LeaveRequest("王五", 30);
            directLeaveHandler.process(yongMing);
        }
    

    这里我们创建了一个直属领导, 一个部门经理,一个总经理. 并设置了上下级关系.
    然后根据员工请假的天数来判断, 应该如何审批.
    对于用户而言,他不需要知道前面有多少个领导需要审批. 他只需要提交给第一个领导, 也就是直属领导, 然后不断往下走审批就可以了. 也就是说,在责任链设计模式中,我们只需要拿到链上的第一个处理者,那么链上的每个处理者都有机会处理相应的请求。

    以上代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我们会继续优化责任链设计模式。

    第五步: 查看结果

    由于请假是随机了, 还有可能被驳回. 我们先来看看全部同意的请求结果

    ========张三请假2天==========
    直属主管审批完成
    ========李四请假6天==========
    直属主管审批完成
    部门经理审批完成
    ========王五请假30天==========
    直属主管审批完成
    部门经理审批完成
    总经理审批完成
    

    再来看看有驳回的请求结果

    ========张三请假2天==========
    直属主管审批驳回
    ========李四请假6天==========
    直属主管审批驳回
    ========王五请假30天==========
    直属主管审批完成
    部门经理审批驳回
    

    4. 责任链概念抽象总结

    责任链设计模式: 客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。 将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止

    上面的代码基本上概括了责任链设计模式的使用,但是上述客户端的代码其实也是很繁琐的,后面我优化责任链设计模式。

    4. 责任链设计模式的优缺点

    优点

    动态组合,使请求者和接受者解耦。
    请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。
    动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。

    缺点

    产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
    不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性。

    四. 综合案例 -- 网关权限控制

    1. 明确需求

    网关有很多功能: API接口限流, 黑名单拦截, 权限验证, 参数过滤等. 下面我们就通过责任链设计模式来实现网关权限控制。

    2. 实现思路

    来看一下下面的类图.

    可以看到定义了一个抽象的网关处理器. 然后有4个子处理器的实现类.

    3. 具体实现

    第一步: 定义抽象的网关处理器类

    /**
     * 定义抽象的网关处理器类
     */
    public abstract class AbstractGatewayHandler {
        /**
         * 定义下一个网关处理器
         */
        protected AbstractGatewayHandler nextGatewayHandler;
    
        public void setNextGatewayHandler(AbstractGatewayHandler nextGatewayHandler) {
            this.nextGatewayHandler = nextGatewayHandler;
        }
    
        /**
         * 抽象网关执行的服务
         * @param url
         */
        public abstract void service(String url);
    }
    

    第二步: 定义具体的网关服务

    1. API接口限流处理器

    /**
     * API接口限流处理器
     */
    public class APILimitGatewayHandler extends AbstractGatewayHandler {
        @Override
        public void service(String url) {
            System.out.println("api接口限流处理, 处理完成");
            // 实现具体的限流服务流程
            if (this.nextGatewayHandler != null) {
                this.nextGatewayHandler.service(url);
            }
        }
    }
    

    2. 黑名单拦截处理器

    /**
     * 黑名单处理器
     */
    public class BlankListGatewayHandler extends AbstractGatewayHandler {
        @Override
        public void service(String url) {
            System.out.println("黑名单处理, 处理完成");
    
            // 实现具体的限流服务流程
            if (this.nextGatewayHandler != null) {
                this.nextGatewayHandler.service(url);
            }
        }
    }
    

    3. 权限验证处理器

    /**
     * 权限验证处理器
     */
    public class PermissionValidationGatewayHandler extends AbstractGatewayHandler {
        @Override
        public void service(String url) {
            System.out.println("权限验证处理, 处理完成");
            // 实现具体的限流服务流程
            if (this.nextGatewayHandler != null) {
                this.nextGatewayHandler.service(url);
            }
        }
    }
    

    4. 参数校验处理器

    /**
     * 参数校验处理器
     */
    public class ParameterVerificationGatewayHandler extends AbstractGatewayHandler {
        @Override
        public void service(String url) {
            System.out.println("参数校验处理, 处理完成");
            // 实现具体的限流服务流程
            if (this.nextGatewayHandler != null) {
                this.nextGatewayHandler.service(url);
            }
        }
    }
    

    第三步: 定义网关客户端, 设置网关请求链

    /**
     * 网关客户端
     */
    public class GatewayClient {
        public static void main(String[] args) {
            APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
            BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
            ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
            PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();
    
            apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
            blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
            parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);
            
            apiLimitGatewayHandler.service("http://www.baidu.com");
        }
    }
    

    这里和之前差不多, 不做太多解释了, 来看运行效果:

    api接口限流处理, 处理完成
    黑名单处理, 处理完成
    参数校验处理, 处理完成
    权限验证处理, 处理完成
    

    这样就进行了一系列的网关处理. 当然, 每一次处理都应该返回处理结果, 然后决定是否进行下一次处理. 这里就简化了

    第四步: 使用工厂模式优化责任链设计模式

    在第三步网关客户端中,对责任链进行了初始化操作。 这样, 每次客户端想要发起请求都需要执行一遍初始化操作, 其实完全没有这个必要. 我们可以使用工厂设计模式, 将客户端抽取到工厂中, 每次只需要拿到链上的第一个处理者就可以了.

    1. 定义网关处理器工厂

    /**
     * 网关处理器工厂
     */
    public class GatewayHandlerFactory {
        public static AbstractGatewayHandler getFirstGatewayHandler() {
            APILimitGatewayHandler apiLimitGatewayHandler = new APILimitGatewayHandler();
            BlankListGatewayHandler blankListGatewayHandler = new BlankListGatewayHandler();
            ParameterVerificationGatewayHandler parameterVerificationGatewayHandler = new ParameterVerificationGatewayHandler();
            PermissionValidationGatewayHandler permissionValidationGatewayHandler = new PermissionValidationGatewayHandler();
    
            apiLimitGatewayHandler.setNextGatewayHandler(blankListGatewayHandler);
            blankListGatewayHandler.setNextGatewayHandler(parameterVerificationGatewayHandler);
            parameterVerificationGatewayHandler.setNextGatewayHandler(permissionValidationGatewayHandler);
    
            return apiLimitGatewayHandler;
        }
    }
    

    网关处理器工厂定义了各个网关处理器之间的关系, 并返回第一个网关处理器.

    2.优化网关客户端

    /**
     * 网关客户端
     */
    public class GatewayClient {
        public static void main(String[] args) {
            GatewayHandlerFactory.getFirstGatewayHandler().service("http://www.baidu.com");
        }
    }
    

    我们在客户端只需要直接调用第一个网关处理器就可以了, 不需要关心其他的处理器.

    五. 责任链模式总结

    1. 定义一个抽象的父类, 在抽象的父类中定义请求处理的方法 和 下一个处理者.
    2. 然后子类处理器继承分类处理器, 并实现自己的请求处理方法
    3. 设置处理请求链, 可以采用工厂设计模式抽象, 请求者只需要知道整个链条的第一环


  • 相关阅读:
    Golang-数据类型-int类型
    Golang基础知识-变量
    16.和input相关的知识点
    14.ajax基础知识、用ajax做登录页面、用ajax验证用户名是否可用、ajax动态调用数据库
    12.登录页面左右切换。
    10.用js下载文件(需要后端链接)
    9.用js制作静态分页
    8.一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
    7.前端性能优化的方法
    iOS
  • 原文地址:https://www.cnblogs.com/ITPower/p/14929883.html
Copyright © 2011-2022 走看看