浅析设计模式 -- 责任链模式

news2024/10/7 7:25:36

目录

前言

概述

基本概念

▐  结构

▐  使用

使用示例

▐  代码实现​​​​​​​

▐  结果输出

▐  UML图

扩展

源码赏析

优缺点及适用场景

▐  优点

▐  缺点

▐  适用场景


前言

        我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。本专题着眼于实际开发过程中常用的几种设计模式,从理论和实战两个角度进行讨论和分享,力求逻辑清晰、表述简洁,帮助大家在项目中合理运用设计模式,保障代码的可靠性。

概述

        我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度,而设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。

        大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。其中,行为型模式可用于描述程序中多个类和多个对象如何协作完成复杂的任务,涉及不同对象间的职责分配、算法的抽象化。责任链模式是一种典型的行为型模式,本文将着眼于责任链模式进行学习分享,阐述责任链模式的核心概念和应用实践。认识和使用责任链模式,可以在开发中有效增强应用的灵活性和可扩展性。

基本概念

        责任链模式的核心思想是:将请求的每个处理者都视为一个处理节点,再将这些节点连成一条链,当请求到来后便可沿着这条链进行传递,直到有节点处理它为止。这种模式可以有效避免请求发送者和请求接受者之间的耦合关系,用户只需要将请求发送到责任链上,不用关心传递过程和处理细节。

下面从模式结构和使用步骤两个方面,简单阐述责任链方法模式的基本概念。

▐  结构

        责任链模式的结构也比较简单易懂,主要包含三大类:抽象处理者类、具体处理者类和客户类,抽象处理者类中首先定义好抽象方法和后继处理机制,具体的处理方法实现将在具体处理者中执行,而客户类将对所有定义的具体处理者组装成链,让请求从链头开始沿着责任链向后执行。

角色

关系

作用

抽象处理者

Handler

具体处理者的父类

定义一个处理请求的接口,

包含抽象处理方法和一个后继连接。

具体处理者

Concrete Handler

抽象构件的接口实现类

实现抽象处理者的抽象处理方法,

判断能否处理本次请求,如果可以则处理,

否则将转交给其后继者。

客户类

Client

将具体处理者组装成责任链

创建处理链,并向链头的具体处理者提交请求,

它不关心处理细节和请求的传递过程。

▐  使用

基于上述基本概念,将装饰者模式的使用步骤概括为:

step1:创建抽象处理者类,定义一个抽象方法和一个指向下一处理者节点的指针 next。

step2:创建具体处理者类,实现抽象处理者类中定义的抽象方法;

step3:创建客户类,将各个具体处理者类组装成责任链,将请求提交给链头的具体处理者类。

使用示例

        其实,大家平常接触到的各种审批流,就涉及多个审批节点,不同的审批节点负责人分别持有不同级别的审批权限。比如:请假审批流、紧急发布审批流、报销审批流等等。疫情管控时期,我们学校为了确保在校师生及其他员工的安全,会严格要求学生不能随意出入校门,如果有特殊情况如看病就医,则需在校务系统中进行申请,系统会根据学生请假出校的时长,设置不同级别的负责人进行严格审批。这一过程,用责任链模式就可以实现。

▐  代码实现​​​​​​​

// 创建抽象处理者类,定义指向下一节点的指针和抽象处理方法
public abstract class AbstractHandler {
    private AbstractHandler next;
    public void setNext(AbstractHandler next) {
        this.next = next;
    }
    public AbstractHandler getNext() {
        return next;
    }
    public abstract void handleRequest(int leaveDayNum);
}

// 定义具体处理者类1:系统自动审批
public class ConcreteHandler1 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 1) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 学校自动审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}

// 定义具体处理中类2:导师审批
public class ConcreteHandler2 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 3) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 导师审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}

// 定义具体处理者类3:辅导员审批
public class ConcreteHandler3 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 5) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 辅导员审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}

// 定义具体处理者类4:院长审批
public class ConcreteHandler4 extends AbstractHandler {
    @Override
    public void handleRequest(int leaveDayNum) {
        if (leaveDayNum <= 7) {
            System.out.println("请假不超过" + leaveDayNum + "天" + ": 院长审批通过");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(leaveDayNum);
            } else {
                System.out.println("请假天数过长,需向学院提供签字承诺书");
            }
        }
    }
}

//定义客户类:组装责任链,向链头节点发送请求
public class chainOfResponsibility {
    public static void main(String[] args) {
        AbstractHandler handler1 = new ConcreteHandler1();
        AbstractHandler handler2 = new ConcreteHandler2();
        AbstractHandler handler3 = new ConcreteHandler3();
        AbstractHandler handler4 = new ConcreteHandler4();
        handler1.setNext(handler2);
        handler2.setNext(handler3);
        handler3.setNext(handler4);
        handler1.handleRequest(6);
    }
}

▐  结果输出

请假不超过6天: 院长审批通过

▐  UML图

扩展

        很多文章在介绍责任链模式时都会提到一个概念:纯的责任链模式、不纯的责任链模式,这里也做一个简单的扩展。

1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下一个具体处理者处理。

2. 不纯的职责链模式:允许出现某一个具体处理者在承担了请求的部分责任后,再将剩余责任传给下一个具体处理者继续处理剩余部分责任。

        另外,在日常使用中,Handler 不是一定要提供一个设置后继处理器的接口,可以把维护链路的职责独立出来,如下图所示。

        在这种责任链模式中,HandlerChain 来维护整条链路,它提供了增删处理者的方法,并实现了抽象处理者类的接口,负责在责任链中传递请求 Request。同时,HandlerChain 内部维护了当前具体处理者在整个责任链中的索引。当客户类发出请求后,HandlerChain 会驱动第一个具体处理者(pos = 0),并根据需要判断是否继续向后传递,若需要则调用 handle() 方法再次驱动下一个具体处理者(pos 在上一次驱动时自增),若不需要则跳出链路;如此反复,直到有具体处理者判断不需再向后传递,或已经执行到最后一个具体处理者。

        这样,每个具体处理者不再依赖后继处理器,而是各自在 HandlerChain 中的位置 pos,从而增强模式的灵活性,解除具体处理者之间的依赖性。Servlet 中的过滤器 Filter 就是基于这种方式实现的。

源码赏析

        Servlet 约定在请求进入容器后、执行 Servlet.service() 方法前,可以通过 Filter 对 web 资源进行过滤、权限鉴别等处理。在 tomcat 启动时,先加载所有的过滤器信息;在 tomcat 收到请求时,再加载并执行整个过滤器的链路,当请求从链路中脱离后,才会进入真正的业务接口,如下图所示。

        在 tomcat 中,每一个 Filter 都是一个具体处理者,不仅能处理请求、还能处理响应。对于 request 来说,责任链结构为 Filter1 -> Filter2 -> Filter3;而对于响应来说,责任链结构为 Filter3 -> Filter2 -> Filter1。这种双向处理的思想其实也很经典,下面将简单分析一下源码。​​​​​​​

public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
    public default void destroy() {}
}

        Filter 为抽象处理者,提供了三个方法,其中 doFilter() 方法为处理方法,三个参数分别为请求、响应和链路管理器。接下来看一下链路管理器 FilterChain 的源码。

// FilterChain
public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;
}

// ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {    
    // 处理器链路,ApplicationFilterConfig 可以理解为对 Filter 的包装
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];   
    // 当前处理器在链路中的索引
    private int pos = 0;    
    // 调用入口
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            // here
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            // here
            internalDoFilter(request,response);
        }
    }
    
    // 开始处理
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
        // 不停的驱动下一个过滤器
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    // 驱动过滤器
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // 链路中没有更多过滤器了,开始进入 servlet
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }
            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                // 进入servlet 处理实际业务
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }
}

        ApplicationFilterChain 用数组来组织各处理者的先后顺序,并提供一个当前处理者在链路中的索引 pos 。当链路未在中途断开且当前处理者已是最后一个处理者时,调用 Servlet.service(request, response) 进入业务处理逻辑。作为用户,我们可以通过配置文件或者注入等方式,根据需要定义新的 Filter。

优缺点及适用场景

▐  优点

1. 降低对象之间的耦合度。一个节点对象无须关心链的结构、到底是哪一个对象处理其请求,发送者和接收者也无须拥有对方的明确信息。

2. 增强系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

3. 灵活地为对象指派职责。当工作流程变化时,可动态改变节点或调动节点次序,也可动态增删节点。

4. 简化节点之间的连接。各节点只需保持一个指向其后继者的引用,避免使用众多 if 或 if…else 语句。

5. 责任分担,符合类的单一职责原则。每个节点类只需处理自己该处理的工作,不该处理的传递给下一个节点类完成,各类的责任范围非常明确。

▐  缺点

1. 不能保证每个请求一定被处理。一个请求没有明确的接收者,可能一直传到链的末端都得不到处理。

2. 当责任链太长时,一个请求可能需要涉及多个处理者,系统性能会受到一定影响。

3. 责任链建立的合理性需要由客户端来保证,增加了客户端的复杂性,也可能会因为错误设置而导致系统陷入死循环。

▐  适用场景

1. 在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。

2. 不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。

3. 需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。

4. 职责链模式常被用在框架开发中,实现过滤器、拦截器等功能,使用者可以在不修改源码的情况下,添加新的过滤拦截功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/632784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

Shell脚本学习记录(常见指令)

Shell echo命令 Shell 的 echo 指令与 PHP 的 echo 指令类似&#xff0c;都是用于字符串的输出。命令格式&#xff1a; echo string 1.显示普通字符串: echo "It is a test" //双引号省略效果相同 2.显示转义字符 echo "\"It is a test\"" …

chatgpt赋能python:Python取出字典中键名对应的值

Python取出字典中键名对应的值 作为一个有10年Python编程经验的工程师&#xff0c;我经常遇到需要从字典中取出键名对应的值的情况。在这篇文章中&#xff0c;我将介绍Python中几种不同的方法来执行这个任务&#xff0c;并提供有关每个方法的优缺点的评估。让我们开始吧&#…

chatgpt赋能python:Python怎么取余数?

Python怎么取余数&#xff1f; 在Python中&#xff0c;我们经常需要进行数学运算&#xff0c;而其中计算余数也是经常用到的。虽然计算余数的方法似乎很简单&#xff0c;但是在不同的编程语言中有些微小的差异。本文将介绍在Python中如何高效地计算余数&#xff0c;以及一些相…

【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)

【动态规划】NK刷题记之DP6 连续子数组最大和&#xff08;C语言实现&#xff09; 一、题目二、题解 2.1动态规划2.2贪心算法2.1.1 贪心算法的定义2.2.2贪心算法的性质2.2.3本题的贪心算法解决思路 2.2.4贪心与动态规划的区别 三、代码实现 3.1法一&#xff1a;动态规划(递归实…

无需公网IP,在家使用IPV6和电信光猫进行内网穿透以搭建远程主机

ipv4的公网IP弄起来还是比较麻烦&#xff0c;所以不管是搭建私人NAS还是远程登陆主机都总是需要进行内网穿透。一般的方案都是用花生壳这类的商用服务&#xff0c;然而这些服务一方面又贵又慢还有流量限制&#xff0c;另一方面还要进行把三代信息都盘出去的实名认证 1G到5G一个…

System V通信

文章目录 共享内存什么是共享内存&#xff08;物理内存块属性&#xff09;共享内存的接口认识查看共享内存删除共享内存共享内存的创建&#xff08;ftok和shmget&#xff09;挂接和去关联&#xff08;shmat和shmdt&#xff09; 利用共享内存通信&#xff08;简单的代码演示&…

Vue中如何进行数据缓存

Vue中如何进行数据缓存 Vue是一款流行的前端框架&#xff0c;它提供了许多方便的功能来处理数据。其中一个非常有用的功能是数据缓存。数据缓存可以提高应用程序的性能&#xff0c;减少网络请求&#xff0c;提高用户体验。在本文中&#xff0c;我们将介绍Vue中如何进行数据缓存…

chatgpt赋能python:Python如何取三位小数

Python 如何取三位小数 Python 是一种很强大的编程语言&#xff0c;可以应用于各个领域。其中&#xff0c;处理数字也是 Python 的一项强大功能。当我们需要对数字进行精细的操作时&#xff0c;常常需要使用到取小数的功能。本文将介绍如何使用 Python 取三位小数&#xff0c;…

Qgis中进行Shp和Excel属性连接实现百强县公共预算空间分析

前言 在之前的博文中&#xff0c;将2022的全国百强县一般公共预算收入的数据下载到了本地&#xff0c;博客原文地址&#xff1a;一种使用Java的快速将Web中表格转换成Excel的方法。对于不关注时空位置关系的一般分析&#xff0c;到此也就基本够用了。但是&#xff0c;如果站在全…

C语言函数初阶(1)

目录 1. 函数是什么 2. 库函数 3. 自定义函数 4. 函数参数 5. 函数调用 6. 函数的嵌套调用和链式访问 7. 函数的声明和定义 8. 函数递归 今天我们讲解前6个部分&#xff0c;下一个博客我们讲解后2个部分&#xff0c;因为后两个部分难度较大&#xff0c;讲解起来要花一点…

Vue中如何进行错误处理

Vue中如何进行错误处理 在Vue应用程序中&#xff0c;错误处理是必不可少的。错误可能发生在各种地方&#xff0c;例如网络请求、组件生命周期钩子函数、计算属性、方法等等。如果我们不正确地处理这些错误&#xff0c;可能会导致应用程序崩溃或无法正常工作。在本文中&#xf…

chatgpt赋能python:Python怎么反向切片

Python怎么反向切片 在Python中&#xff0c;切片是一种用于从序列中选取子序列的方法。正向切片从序列的第一个元素开始选取&#xff0c;而反向切片则从序列的最后一个元素开始选取。本文将介绍Python中如何使用反向切片。 什么是切片 在Python中&#xff0c;切片是一种操作…

IP协议的特性总结

目录 1. 地址管理 1.1 动态分配 1.2 NAT(网络地址转换)机制 1.3 IP地址的组成 1.4 IP地址网络号和主机号的划分 1.4.1 IP地址分类(ABCDE类) 1.4.2 子网掩码 1.5 特殊的IP地址 2. 路径规划 3. IP协议报文格式 3.1 分包 3.2 组包 1. 地址管理 IP地址在之前跟大家简单…

mfc读取obj格式文件初步

3dmax做一个box&#xff1b; 导出为cube1.obj&#xff1b; 记事本打开看一下该obj文件&#xff1b; # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware # File Created: 10.06.2023 23:16:04mtllib cube1.mtl# # object Box001 #v -41.2323 0.0000 31.8849 v -4…

chatgpt赋能python:Python如何反向排序

Python如何反向排序 在Python中&#xff0c;排序是一项常见的任务。通常情况下&#xff0c;我们想对一组数据按照升序进行排序。但有时候&#xff0c;我们需要对这些数据进行反向排序&#xff0c;也就是按照降序进行排序。那么&#xff0c;Python该如何实现反向排序呢&#xf…

chatgpt赋能python:Python如何取出int内的个位数

Python如何取出int内的个位数 Python已经成为全球范围内最受欢迎的编程语言之一&#xff0c;它具有简单易学&#xff0c;可读性高和可扩展性等特点&#xff0c;因此它被广泛应用于数据科学、人工智能、网络编程、物联网和Web开发等领域。在Python编程中&#xff0c;有时需要从…

第七十天学习记录:高等数学:微分(宋浩板书)

微分的定义 基本微分公式与法则 复合函数的微分 微分的几何意义 微分在近似计算中应用 sin(xy) sin(x)cos(y) cos(x)sin(y)可以用三角形的几何图形来进行证明。 假设在一个单位圆上&#xff0c;点A(x,y)的坐标为(x,y)&#xff0c;点B(x’, y’)的坐标为(x’, y’)。则以两点…

44--Django-项目实战-全栈开发-基于django+drf+vue+elementUI企业级项目开发流程-支付宝二次封装、支付成功页面以及后台设计

一、支付宝支付介绍 需求:购买课程,付款 现在主流支付有支付宝支付、微信支持、银联支付 申请使用支付宝支付,需要有商户号(用户把钱付款到你的商户号中) 收手续费商户号要申请,需要有公司的营业执照(不需要营业执照也可以申请–》笔记)我们开发,需要商户号,公钥,…

Spring的数据访问哲学

目录 设计思路 了解Spring的数据访问异常体系 数据访问模板化 设计思路 Spring的目标之一就是允许我们在开发应用程序时&#xff0c;能够遵循面向对象(OO)原则中的“针对接口编程”Spring对数据访问的支持也不例外像很多应用程序一样&#xff0c;Spittr应用需要从某种类型的…