设计模式 - 责任链
一、前言
相信大家平时或多或少都间接接触过责任链设计模式,只是可能有些同学自己不知道此处用的是该设计模式,比如说 Java Web 中的 Filter 过滤器,就是非常经典的责任链设计模式的例子。
那么什么是责任链设计模式呢?
客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
技术领域的相关定义总是那样的晦涩难懂,因此不理解不重要,下面将会用例子来帮助理解。
责任链模式有哪些优点,能解决什么问题,我们为什么要使用它呢?
优点:
- 将请求与处理解耦。
- 请求处理对象只需关注自己需要处理的请求进行处理即可,对于不需要自己处理的请求,直接转发给下一个处理对象即可,符合单一职责原则。
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
- 链路结构灵活,可以通过改变链路结构动态的新增或者删除处理对象。即易于拓展新的请求处理类,符合开闭原则。
缺点:
- 会存在责任链太长,而大多数处理者都不会对请求进行处理的情况,导致走完整个责任链的时间太长,影响整体性能。
- 如果责任链配置不完善,会存在处理对象循环引用,从而造成死循环,导致系统崩溃的情况。
适用场景:
- 多条件流程判断,如权限控制
- ERP 系统流程审批
- Java Web 过滤器的底层实现 Filter
- Mybatis 中的分页插件 PageHelper
二、代码示例
1. 导包信息
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version>
</dependency>
2. 代码结构
3. 具体代码
流程扩展类(主要记录每个流程的上一个处理对象和下一个处理对象的信息) ProcessDTO.java:
package com.dxc.responsibility.dto;import lombok.AllArgsConstructor;
import lombok.Data;/*** 流程扩展类** @Author xincheng.du* @Date 2023/8/30 14:26*/
@Data
@AllArgsConstructor
public class ProcessDTO {/*** 流程处理器id*/private Integer handlerId;/*** 全限定名*/private String fullName;/*** 上一个流程处理器id*/private Integer preHandlerId;/*** 下一个流程处理器id*/private Integer nextHandlerId;}
流程链枚举 ProcessChainEnum.java
package com.dxc.responsibility.enums;import com.dxc.responsibility.dto.ProcessDTO;
import lombok.AllArgsConstructor;/*** 流程链枚举* 此处的枚举类可以换成数据库配置,当然你也可以理解为数据库表中的一条条数据* 这样就可以根据更改枚举类或数据库中的顺序,来更改实际处理流程中的执行顺序了** @Author xincheng.du* @Date 2023/8/30 14:26*/
@AllArgsConstructor
public enum ProcessChainEnum {/*** 流程链中的第一个流程*/FIRST(new ProcessDTO(1, "com.dxc.responsibility.handler.impl.FirstProcessHandler", null, 2)),/*** 流程链中的第二个流程*/SECOND(new ProcessDTO(2, "com.dxc.responsibility.handler.impl.SecondProcessHandler", 1, 3)),/*** 流程链中的第三个流程*/THIRD(new ProcessDTO(3, "com.dxc.responsibility.handler.impl.ThirdProcessHandler", 2, null)),;ProcessDTO processDTO;public ProcessDTO getProcessDTO() {return processDTO;}}
流程处理器工厂(主要用来初始化流程链,并返回第一个流程处理器) ProcessHandlerFactory.java
package com.dxc.responsibility.factory;import cn.hutool.core.util.ReflectUtil;
import com.dxc.responsibility.dto.ProcessDTO;
import com.dxc.responsibility.handler.AbstractProcessHandler;
import com.dxc.responsibility.handler.impl.FirstProcessHandler;
import com.dxc.responsibility.service.ProcessService;
import com.dxc.responsibility.service.impl.ProcessServiceImpl;
import lombok.extern.slf4j.Slf4j;/*** 流程处理器工厂** @Author xincheng.du* @Date 2023/8/30 14:26*/
@Slf4j
public class ProcessHandlerFactory {private ProcessHandlerFactory() {}private static final ProcessService processService = new ProcessServiceImpl();/*** 初始化流程链,并返回流程链中第一个流程处理器** @return {@link FirstProcessHandler}*/public static FirstProcessHandler getFirstProcessHandler() {// 获取第一个流程扩展类ProcessDTO firstProcessDTO = processService.getFirstProcessDTO();// 根据流程扩展类获取第一个流程处理器AbstractProcessHandler firstProcessHandler = newProcessHandler(firstProcessDTO);ProcessDTO tempProcessDTO = firstProcessDTO;Integer nextHandlerId;AbstractProcessHandler tempProcessHandler = firstProcessHandler;// 迭代遍历所有handler,以及将它们链接起来while ((nextHandlerId = tempProcessDTO.getNextHandlerId()) != null) {// 根据处理器id获取流程扩展类ProcessDTO processDTO = processService.getProcessEntity(nextHandlerId);// 根据流程扩展类获取具体的流程处理器AbstractProcessHandler processHandler = newProcessHandler(processDTO);assert tempProcessHandler != null;tempProcessHandler.setNext(processHandler);tempProcessHandler = processHandler;tempProcessDTO = processDTO;}// 返回第一个handlerreturn (FirstProcessHandler) firstProcessHandler;}/*** 根据流程扩展类获取具体的流程处理器** @param dto 流程扩展类* @return {@link AbstractProcessHandler}*/private static AbstractProcessHandler newProcessHandler(ProcessDTO dto) {// 获取全限定类名String className = dto.getFullName();try {// 根据全限定类名,加载并初始化该类Class<?> clazz = Class.forName(className);return (AbstractProcessHandler) ReflectUtil.newInstance(clazz);} catch (ClassNotFoundException e) {log.error("根据流程扩展类获取流程处理器失败,原因:{},流程处理器:{}", e.getMessage(), dto.getFullName());e.printStackTrace();}return null;}}
抽象流程处理器(主要是用来定义每个具体处理的方法模板,不做具体逻辑处理,具体的流程处理器需要集成当前抽象类,并实现各自的流程处理逻辑) AbstractProcessHandler.java
package com.dxc.responsibility.handler;/*** 流程处理抽象类** @Author xincheng.du* @Date 2023/8/31 11:25*/
public abstract class AbstractProcessHandler {/*** 下一关用当前抽象类来接收*/protected AbstractProcessHandler next;public void setNext(AbstractProcessHandler next) {this.next = next;}/*** 具体处理逻辑* 需要子类实现*/public abstract void process();}
第一个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) FirstProcessHandler.java
package com.dxc.responsibility.handler.impl;import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;/*** 第一个流程处理器** @Author xincheng.du* @Date 2023/8/30 11:25*/
@Slf4j
public class FirstProcessHandler extends AbstractProcessHandler {@Overridepublic void process() {log.info("第一个流程处理开始对请求进行处理......");if (this.next != null) {this.next.process();}}}
第二个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) SecondProcessHandler.java
package com.dxc.responsibility.handler.impl;import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;/*** 第二个流程处理器** @Author xincheng.du* @Date 2023/8/30 11:25*/
@Slf4j
public class SecondProcessHandler extends AbstractProcessHandler {@Overridepublic void process() {log.info("第二个流程处理开始对请求进行处理......");if (this.next != null) {this.next.process();}}}
第三个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) SecondProcessHandler.java
package com.dxc.responsibility.handler.impl;import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;/*** 第三个流程处理器** @Author xincheng.du* @Date 2023/8/30 11:25*/
@Slf4j
public class ThirdProcessHandler extends AbstractProcessHandler {@Overridepublic void process() {log.info("第三个流程处理开始对请求进行处理......");if (this.next != null) {this.next.process();}}}
流程扩展类接口(主要对流程扩展类进行封装初始化,为流程处理器工厂提供方法) ProcessService.java
package com.dxc.responsibility.service;import com.dxc.responsibility.dto.ProcessDTO;/*** 流程扩展类 接口** @Author xincheng.du* @Date 2023/8/30 14:26*/
public interface ProcessService {/*** 根据流程处理器id获取流程扩展类** @param handlerId 流程处理器id* @return {@link ProcessDTO}*/ProcessDTO getProcessEntity(Integer handlerId);/*** 获取第一个流程扩展类** @return {@link ProcessDTO}*/ProcessDTO getFirstProcessDTO();}
流程扩展类接口的具体实现 ProcessServiceImpl.java
package com.dxc.responsibility.service.impl;import com.dxc.responsibility.dto.ProcessDTO;
import com.dxc.responsibility.enums.ProcessChainEnum;
import com.dxc.responsibility.service.ProcessService;
import lombok.extern.slf4j.Slf4j;import java.util.HashMap;
import java.util.Map;/*** 流程扩展类 逻辑处理** @Author xincheng.du* @Date 2023/8/30 14:26*/
@Slf4j
public class ProcessServiceImpl implements ProcessService {/*** 流程扩展类Map* key=流程处理器id value=流程扩展类 ProcessDTO*/private static Map<Integer, ProcessDTO> processDTOMap = new HashMap<>();/*** 流程链初始化* 将枚举中配置的handler初始化到map中,方便获取*/static {ProcessChainEnum[] values = ProcessChainEnum.values();for (ProcessChainEnum value : values) {ProcessDTO processDTO = value.getProcessDTO();processDTOMap.put(processDTO.getHandlerId(), processDTO);}}@Overridepublic ProcessDTO getProcessEntity(Integer handlerId) {return processDTOMap.get(handlerId);}@Overridepublic ProcessDTO getFirstProcessDTO() {for (Map.Entry<Integer, ProcessDTO> entry : processDTOMap.entrySet()) {ProcessDTO value = entry.getValue();// 没有上一个handler的就是第一个if (value.getPreHandlerId() == null) {return value;}}log.error("获取第一个流程扩展类出错");return null;}
}
客户端(相当于请求发起者) ProcessClient.java
package com.dxc.responsibility;import com.dxc.responsibility.factory.ProcessHandlerFactory;
import com.dxc.responsibility.handler.AbstractProcessHandler;/*** 客户端** @Author xincheng.du* @Date 2023/8/30 14:26*/
public class ProcessClient {public static void main(String[] args) {// 获取第一个流程处理器AbstractProcessHandler firstProcessHandler = ProcessHandlerFactory.getFirstProcessHandler();assert firstProcessHandler != null;// 执行第一个流程处理器firstProcessHandler.process();}}
4. 运行结果
相关文章:

设计模式 - 责任链
一、前言 相信大家平时或多或少都间接接触过责任链设计模式,只是可能有些同学自己不知道此处用的是该设计模式,比如说 Java Web 中的 Filter 过滤器,就是非常经典的责任链设计模式的例子。 那么什么是责任链设计模式呢? …...

【小沐学Unity3d】3ds Max 骨骼动画制作(CAT、Character Studio、Biped、骨骼对象)
文章目录 1、简介2、 CAT2.1 加载 CATRig 预设库2.2 从头开始创建 CATRig 3、character studio3.1 基本描述3.2 Biped3.3 Physique 4、骨骼系统4.1 创建方法4.2 简单示例 结语 1、简介 官网地址: https://help.autodesk.com/view/3DSMAX/2018/CHS https://help.aut…...

CUDA说明和安装[window]
文章目录 1、查看版本信息查看GPU查看cuda版本其他方法 2区分 了解cudaCUDA ToolkitNVCCcuDNN 3/ 安装过程4/版本的问题CUDA Toolkit和 显卡驱动 的版本对应CUDA / CUDA Toolkit和cuDNN的版本对应 5/关于CUDA和Cudnn**5.1 CUDA的命名规则****5.2 如何查看自己所安装的CUDA的版本…...

sqlserver2012性能优化配置:设置性能相关的服务器参数
前言 sqlserver2012 长时间运行的话会将服务器的内存占满 解决办法 通过界面设置 下图中设置最大服务器内存 通过执行脚本设置 需要先开发开启高级选项配置才能设置成功 设置完成之后将高级选择配置关闭,还原成跟之前一样 --可以配置高级选项 EXEC sp_conf…...

介绍 dubbo-go 并在Mac上安装,完成一次自己定义的接口RPC调用
目录 RPC 远程调用的说明作用:像调用本地方法一样调用远程方法和直接HTTP调用的区别:调用模型图示: Dubbo 框架说明Dubbo Go 介绍应用 Dubbo Go环境安装(Mac 系统)安装 Go语言环境安装 序列化工具protoc安装 dubbogo-c…...
目标检测数据集:摄像头成像吸烟检测数据集(自己标注)
1.专栏介绍 ✨✨✨✨✨✨目标检测数据集✨✨✨✨✨✨ 本专栏提供各种场景的数据集,主要聚焦:工业缺陷检测数据集、小目标数据集、遥感数据集、红外小目标数据集,该专栏的数据集会在多个专栏进行验证,在多个数据集进行验证mAP涨点明显,尤其是小目标、遮挡物精度提升明显的…...

Unity的UI管理器
1、代码 public class UIManager {private static UIManager instance new UIManager();public static UIManager Instance > instance;//存储显示着的面板脚本(不是面板Gameobject),每显示一个面板就存入字典//隐藏的时候获取字典中对…...
Mp4文件提取详细H.264和MP3文件
文章目录 Mp4文件提取为H.264和MP3文件**提取视频为H.264:****提取音频为MP3:** 点赞收藏加关注,追求技术不迷路!!!欢迎评论区互动。 Mp4文件提取为H.264和MP3文件 要将视频分开为H.264(视频编…...

Qt应用程序连接达梦数据库-飞腾PC麒麟V10
目录 前言1 安装ODBC1.1 下载unixODBC源码1.2 编译安装1.4 测试 2 编译QODBC2.1 修改 qsqldriverbase.pri 文件2.2 修改 odbc.pro 文件2.3 编译并安装QODBC 3 Qt应用程序连接达梦数据库测试4 优化ODBC配置,方便程序部署4.1 修改pro文件,增加DESTDIR 变量…...

2023-09-03 LeetCode每日一题(消灭怪物的最大数量)
2023-09-03每日一题 一、题目编号 1921. 消灭怪物的最大数量二、题目链接 点击跳转到题目位置 三、题目描述 你正在玩一款电子游戏,在游戏中你需要保护城市免受怪物侵袭。给你一个 下标从 0 开始 且长度为 n 的整数数组 dist ,其中 dist[i] 是第 i …...
绘图 | MATLAB
目的语法注意事项图片中出现网格grid on放在plot后面在同一图片中绘制多个图例hold on在图形中添加图例legend LineSpec 线性 线型描述线型描述" - "实线" : "点线" - - "虚线" -. "点划线 标记 标记描述标记描述“o”圆圈“squa…...

2023年下半年高项考试学习计划
之前总结 2023年上半年的考试,对于我自己,就是虎头蛇尾,也谈不上太过自信,好好学习了一段时间之后,也就是不再发博文,截止到2022年11月的时候,自己就算是放弃了,没有再主动学习。 结…...
SpringBoot中CommandLineRunner的使用
开发中,你有没有遇到这样的场景,项目启动后,立即需要进行一些操作。比如:加载一些初始化数据、执行一段逻辑代码。你可以使用SpringBoot中CommandLineRunner。它可以在项目启动后,执行CommandLineRunner接口实现类的相…...

<OpenCV> Mat属性
OpenCV的图像数据类型可参考之前的博客:https://blog.csdn.net/thisiszdy/article/details/120238017 OpenCV-Mat类型的部分属性如下: size:矩阵的大小, s i z e ( c o l s , r o w s ) size(cols,rows) size(cols,rows)…...
LAMP 综合实验
LAMP 综合实验 一.实验目标 实验目标如下: 实现 LAMP 架构 实现数据库主从复制 实现 NFS 服务器存储 wordpress 文件 实现备份服务器实时备份 NFS 服务器文件 实现日志集中存储 实现 loganalyzer 分析展示日志 二.实验准备 2.1 实验环境 实验环境: 虚拟机版本: VM…...
JavaScript发展历程
目录 一、起源(1995-1997) 二、发展(1997-2005) 三、进化——Ajax与Web 2.0(2005-2010年) 四、移动互联网与现代化(2010年至今) 结论 JavaScript是一种广泛使用的网络编程语言&…...

LP(六十九)智能文档助手升级
本文在笔者之前研发的大模型智能文档问答项目中,开发更进一步,支持多种类型文档和URL链接,支持多种大模型接入,且使用更方便、高效。 项目介绍 在文章NLP(六十一)使用Baichuan-13B-Chat模型构建智能文档中…...
VIM统计搜索关键词命令
:%s/./&/gn 统计字符数 :%s/\i\/&/gn 统计单词数 :%s/^//n 统计行数 :%s/keyword/&/g 统计任何地方出现的 "keyword" :%s/keyword/&/gn 统计任何地方出现的 "keyword" :%s/keyword/:这部分是 Vi…...

0017Java程序设计-spr农业过程化管理系统
摘 要目 录系统设计开发环境 摘 要 本农业过程化管理系统就是建立在充分利用现在完善科技技术这个理念基础之上,并使用IT技术进行对农业过程化的管理,从而保证种植户能种植出优质的农作物,可以实现农业过程化的在线管理,这样保证…...

以可视化方式解释 Go 并发 - 通道
在并发编程中,许多编程语言采用共享内存/状态模型。然而,Go 通过实现 通信顺序进程 (CSP) 区别于众多语言。在 CSP 中,一个程序由并行的进程组成,这些进程不共享状态,而是使用通道进行通信和同步它们的操作。因此&…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...