当前位置: 首页 > news >正文

设计模式 - 责任链

一、前言

​ 相信大家平时或多或少都间接接触过责任链设计模式,只是可能有些同学自己不知道此处用的是该设计模式,比如说 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. 运行结果

在这里插入图片描述

相关文章:

设计模式 - 责任链

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

【小沐学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、简介 官网地址&#xff1a; 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 长时间运行的话会将服务器的内存占满 解决办法 通过界面设置 下图中设置最大服务器内存 通过执行脚本设置 需要先开发开启高级选项配置才能设置成功 设置完成之后将高级选择配置关闭&#xff0c;还原成跟之前一样 --可以配置高级选项 EXEC sp_conf…...

介绍 dubbo-go 并在Mac上安装,完成一次自己定义的接口RPC调用

目录 RPC 远程调用的说明作用&#xff1a;像调用本地方法一样调用远程方法和直接HTTP调用的区别&#xff1a;调用模型图示&#xff1a; Dubbo 框架说明Dubbo Go 介绍应用 Dubbo Go环境安装&#xff08;Mac 系统&#xff09;安装 Go语言环境安装 序列化工具protoc安装 dubbogo-c…...

目标检测数据集:摄像头成像吸烟检测数据集(自己标注)

1.专栏介绍 ✨✨✨✨✨✨目标检测数据集✨✨✨✨✨✨ 本专栏提供各种场景的数据集,主要聚焦:工业缺陷检测数据集、小目标数据集、遥感数据集、红外小目标数据集,该专栏的数据集会在多个专栏进行验证,在多个数据集进行验证mAP涨点明显,尤其是小目标、遮挡物精度提升明显的…...

Unity的UI管理器

1、代码 public class UIManager {private static UIManager instance new UIManager();public static UIManager Instance > instance;//存储显示着的面板脚本&#xff08;不是面板Gameobject&#xff09;&#xff0c;每显示一个面板就存入字典//隐藏的时候获取字典中对…...

Mp4文件提取详细H.264和MP3文件

文章目录 Mp4文件提取为H.264和MP3文件**提取视频为H.264&#xff1a;****提取音频为MP3&#xff1a;** 点赞收藏加关注&#xff0c;追求技术不迷路&#xff01;&#xff01;&#xff01;欢迎评论区互动。 Mp4文件提取为H.264和MP3文件 要将视频分开为H.264&#xff08;视频编…...

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配置&#xff0c;方便程序部署4.1 修改pro文件&#xff0c;增加DESTDIR 变量…...

2023-09-03 LeetCode每日一题(消灭怪物的最大数量)

2023-09-03每日一题 一、题目编号 1921. 消灭怪物的最大数量二、题目链接 点击跳转到题目位置 三、题目描述 你正在玩一款电子游戏&#xff0c;在游戏中你需要保护城市免受怪物侵袭。给你一个 下标从 0 开始 且长度为 n 的整数数组 dist &#xff0c;其中 dist[i] 是第 i …...

绘图 | MATLAB

目的语法注意事项图片中出现网格grid on放在plot后面在同一图片中绘制多个图例hold on在图形中添加图例legend LineSpec 线性 线型描述线型描述" - "实线" : "点线" - - "虚线" -. "点划线 标记 标记描述标记描述“o”圆圈“squa…...

2023年下半年高项考试学习计划

之前总结 2023年上半年的考试&#xff0c;对于我自己&#xff0c;就是虎头蛇尾&#xff0c;也谈不上太过自信&#xff0c;好好学习了一段时间之后&#xff0c;也就是不再发博文&#xff0c;截止到2022年11月的时候&#xff0c;自己就算是放弃了&#xff0c;没有再主动学习。 结…...

SpringBoot中CommandLineRunner的使用

开发中&#xff0c;你有没有遇到这样的场景&#xff0c;项目启动后&#xff0c;立即需要进行一些操作。比如&#xff1a;加载一些初始化数据、执行一段逻辑代码。你可以使用SpringBoot中CommandLineRunner。它可以在项目启动后&#xff0c;执行CommandLineRunner接口实现类的相…...

<OpenCV> Mat属性

OpenCV的图像数据类型可参考之前的博客&#xff1a;https://blog.csdn.net/thisiszdy/article/details/120238017 OpenCV-Mat类型的部分属性如下&#xff1a; size&#xff1a;矩阵的大小&#xff0c; s i z e ( c o l s , r o w s ) size(cols,rows) size(cols,rows)&#xf…...

LAMP 综合实验

LAMP 综合实验 一.实验目标 实验目标如下&#xff1a; 实现 LAMP 架构 实现数据库主从复制 实现 NFS 服务器存储 wordpress 文件 实现备份服务器实时备份 NFS 服务器文件 实现日志集中存储 实现 loganalyzer 分析展示日志 二.实验准备 2.1 实验环境 实验环境: 虚拟机版本: VM…...

JavaScript发展历程

目录 一、起源&#xff08;1995-1997&#xff09; 二、发展&#xff08;1997-2005&#xff09; 三、进化——Ajax与Web 2.0&#xff08;2005-2010年&#xff09; 四、移动互联网与现代化&#xff08;2010年至今&#xff09; 结论 JavaScript是一种广泛使用的网络编程语言&…...

LP(六十九)智能文档助手升级

本文在笔者之前研发的大模型智能文档问答项目中&#xff0c;开发更进一步&#xff0c;支持多种类型文档和URL链接&#xff0c;支持多种大模型接入&#xff0c;且使用更方便、高效。 项目介绍 在文章NLP&#xff08;六十一&#xff09;使用Baichuan-13B-Chat模型构建智能文档中…...

VIM统计搜索关键词命令

:%s/./&/gn 统计字符数 :%s/\i\/&/gn 统计单词数 :%s/^//n 统计行数 :%s/keyword/&/g 统计任何地方出现的 "keyword" :%s/keyword/&/gn 统计任何地方出现的 "keyword" :%s/keyword/&#xff1a;这部分是 Vi…...

0017Java程序设计-spr农业过程化管理系统

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

以可视化方式解释 Go 并发 - 通道

在并发编程中&#xff0c;许多编程语言采用共享内存/状态模型。然而&#xff0c;Go 通过实现 通信顺序进程 (CSP) 区别于众多语言。在 CSP 中&#xff0c;一个程序由并行的进程组成&#xff0c;这些进程不共享状态&#xff0c;而是使用通道进行通信和同步它们的操作。因此&…...

C语言结构体成员大小与偏移量计算原理

1. 结构体成员大小与偏移量获取机制解析1.1 问题背景在C语言编程中&#xff0c;我们经常需要获取结构体成员的大小和偏移量。一个常见的宏定义实现方式如下&#xff1a;// 获取结构体成员大小 #define GET_MEMBER_SIZE(type, member) sizeof(((type*)0)->member)// 获取结构…...

3大革新性功能!VoiceFixer全方位语音修复工具让受损音频焕发新生

3大革新性功能&#xff01;VoiceFixer全方位语音修复工具让受损音频焕发新生 【免费下载链接】voicefixer General Speech Restoration 项目地址: https://gitcode.com/gh_mirrors/vo/voicefixer 你是否遇到过珍贵录音因噪音模糊不清的窘境&#xff1f;是否因会议录音质…...

30/50/20分期怎么设?SAP付款条件Z028实战案例详解(附基准日期避坑指南)

SAP非等额分期付款实战指南&#xff1a;30/50/20比例配置与基准日期避坑 在工程项目、大额设备采购等业务场景中&#xff0c;分期付款是常见的交易方式。不同于标准的等额分期&#xff0c;工程类合同常采用30/50/20这类非对称比例&#xff0c;首期支付30%预付款&#xff0c;中期…...

ESP32嵌入式C++开发:esp-boost工业级Boost库移植指南

1. 项目概述esp-boost是乐鑫&#xff08;Espressif&#xff09;官方主导移植的 Boost C 库子集&#xff0c;专为 ESP 系列 SoC&#xff08;包括 ESP32、ESP32-S3、ESP32-P4、ESP32-C6 等&#xff09;深度定制。它并非简单封装&#xff0c;而是基于 Boost 官方 1.87.0 版本源码进…...

Ubuntu 22.04轻量级桌面环境配置指南:从XFCE到中文输入法一站式解决方案

1. 为什么选择轻量级桌面环境&#xff1f; 很多朋友刚接触Ubuntu时&#xff0c;都会被默认的GNOME桌面惊艳到。但用久了就会发现&#xff0c;这个华丽的界面其实是个"资源大户"。我的老笔记本跑GNOME时&#xff0c;风扇经常呼呼转&#xff0c;开个浏览器都能感觉到卡…...

【CP AUTOSAR】Icu驱动模块:从原理到实战的配置与优化指南

1. Icu驱动模块在AUTOSAR架构中的核心作用 第一次接触AUTOSAR的Icu模块时&#xff0c;我完全被它复杂的配置项搞懵了。直到在S32K3项目上实际调试电机转速测量功能&#xff0c;才真正理解这个模块的价值。简单来说&#xff0c;Icu就像汽车电子系统的"脉搏检测仪"&…...

RHEL 8 部署 Oracle 数据库

目录 一、目标与环境 二、Oracle安装包下载 官方下载地址&#xff08;推荐&#xff09; 三、安装详细步骤 第一阶段&#xff1a;系统准备&#xff08;全部以root用户操作&#xff09; 1. 安装必要的依赖包 2. 创建Oracle用户和组 3. 创建目录结构并设置权限 4. 配置系统…...

基于MATLAB的隔离型DC DC变换器系统设计:技术指标明确、包含设计报告与仿真程序的全过程解析

基于MATLAB的单端反激——隔离型DC/DC变换器系统设计 本设计包括设计报告&#xff0c;仿真程序。技术指标 输入电压、输出电压、输出功率、纹波系数、开关频率见下图凌晨三点盯着示波器的我&#xff0c;突然被显示器上的锯齿状波形逗笑了——这哪儿是DC/DC变换器啊&#xff0c;…...

OpenClaw 配置 scnet API 完整指南 - 被低估的国产大模型 API

OpenClaw 配置 scn# OpenClaw 配置 scnet API 完整指南 写在前面 如果你正在使用 OpenClaw&#xff0c;相信你已经对 AI Agent 有了深入的了解。但在模型选择上&#xff0c;很多人只知道 OpenAI、OpenRouter&#xff0c;却忽视了一个非常优秀的国产选择 —— scnet。 本文将…...

【唠嗑第二嗑-代码里面的无为思想,空空如也的接口】

文章目录接口怎么是空的你当然知道为什么1.定义类型体系&#xff0c;而非行为契约2.为差异化行为预留空间3.真正的实现在子接口中为什么我会惊讶圣人不妄为最近拜读了老子的《道德经》。很多时候觉得读懂了&#xff0c;可转念一想又不是那么回事&#xff01;不知道是老子他老人…...