设计模式 - 责任链

一、前言

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

在这里插入图片描述

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

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

相关文章

大数据课程L6——网站流量项目的SparkStreaming

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解网站流量项目的SparkStreaming概述; ⚪ 掌握网站流量项目的SparkStreaming实现 Wordcount 底层流程; ⚪ 掌握网站流量项目的SparkStreaming实现历史批次的累积处理; ⚪ 掌握网站流…

快速学会git版本管理——上传gitee仓库

首先在gitee右上角有一个新建仓库 创建之后打开自己想要上传的文件 右键打开 Git Bash Here 接下来会弹出git的窗口 首先先初始化仓库 用git命令 git init 然后用git add . 上传所有文件上传到暂存区(上一篇文章说过add是单个文件&#xff0c;add . 是所有文件) 没有显示错误 …

OpenCV_CUDA_VS编译安装

一、OpenCV 我这里是下载的OpenCV4.5.4&#xff0c;但是不知道到在vs里面build时一直报错&#xff0c;后面换了4.7.0的版本测试&#xff0c;安装成功。 Release OpenCV 4.5.4 opencv/opencv GitHub 这个里面有官方预编译好的OpenCV库&#xff0c;可以直接食用。 扩展包&am…

SQL4 查询结果限制返回行数

描述 题目&#xff1a;现在运营只需要查看前2个用户明细设备ID数据&#xff0c;请你从用户信息表 user_profile 中取出相应结果。 示例&#xff1a; iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20北京大学Beijin…

设计模式(1) - UML类图

1、前言 从这一节开始&#xff0c;我们将一起学习设计模式。我们的学习目标是什么呢&#xff1f; 了解常用设计模式以及它们的使用场景&#xff1b;分析实际工程中设计模式的使用&#xff0c;揣摩实际意图&#xff0c;了解作者设计思路&#xff1b;尝试运用设计模式迭代、重构…

css transition 指南

css transition 指南 在本文中&#xff0c;我们将深入了解 CSS transition&#xff0c;以及如何使用它们来创建丰富、精美的动画。 基本原理 我们创建动画时通常需要一些动画相关的 CSS。 下面是一个按钮在悬停时移动但没有动画的示例&#xff1a; <button class"…

MySQL下载安装环境变量配置,常用命令

一、下载安装 mysql官网 下载连接 这个是下载图形安装 https://dev.mysql.com/downloads/installer/ 这个是下载免图形安装 https://dev.mysql.com/downloads/mysql/ 担心个别宝宝没有账号&#xff0c;这边也提供一下&#xff0c;方便下载&#xff1a; 账户&#xff1a;1602404…

算法基础-数学知识-容斥原理、博弈论

容斥原理、博弈论 容斥原理890. 能被整除的数&#xff08;二进制状态压缩版本&#xff0c;复杂度多一个Om&#xff09;890. 能被整除的数&#xff08;dfs版本&#xff09; 博弈论无限制nim游戏AcWing 891. Nim游戏AcWing 892. 台阶-Nim游戏&#xff08;待补&#xff09; 集合版…

Linux中防火墙的简单使用方法

目录 前言 ​编辑 一、概念 1、防火墙的分类&#xff1a; 2、防火墙性能 3、硬件防火墙的品牌、软件防火墙的品牌 4、硬件防火墙与软件防火墙比较 二、linux中的防火墙 1、iptables 2.netfilter/iptables功能 3、四表 iptables中表的优先级 4、五链 三、iptables…

数字化转型背景下企业知识管理能力提升路径

近年来&#xff0c;科技不断进步&#xff0c;颠覆性技术&#xff08;例如 5G、云计算、物联网、大数据分析和人工智能等&#xff09;正在重新定义企业如何管理项目和运营效率。知识管理体系亦需要随着科技的进步而改变&#xff0c;以适应新的数字时代环境&#xff0c;并且高效知…

说说MySQL回表查询与覆盖索引

分析&回答 什么是回表查询&#xff1f; 通俗的讲就是&#xff0c;如果索引的列在 select 所需获得的列中&#xff08;因为在 mysql 中索引是根据索引列的值进行排序的&#xff0c;所以索引节点中存在该列中的部分值&#xff09;或者根据一次索引查询就能获得记录就不需要…

从零开始搭建vite4.0-vue3.0项目

目录 前言 项目地址 项目初始化 git初始化 别名配置 解决vscode报错 vue-router安装 pinia安装 环境配置 axios安装 element-plus按需引入 eslint与prettier安装 scss安装 stylelint配置 代码提交规范配置 husky与lint-stage配置 前言 pnpm和npm的命令行完全一…

FastChat

Fast Chat是一个用于训练/部署和评估基于大型语言模型的聊天机器人的开发平台。其核心功能包括&#xff1a; 最先进模型的权重/训练代码和评估代码(例如Vicuna/FastChat-T5)基于分布式多模型的服务系统&#xff0c;具有Web界面和与OpenAI兼容的RESTful API。 安装 pip instal…

在Cisco设备上配置接口速度和双工

默认情况下&#xff0c;思科交换机将自动协商速度和双工设置。将设备&#xff08;交换机、路由器或工作站&#xff09;连接到 Cisco 交换机上的端口时&#xff0c;将发生协商过程&#xff0c;设备将就传输参数达成一致&#xff0c;当今的大多数网络适配器都支持此功能。 在本文…

八路DI八路DO,开关量远程IO模块,Modbus TCP数据采集模块 YL90-RJ45

特点&#xff1a; ● 八路开关量输入&#xff0c;八路开关量输出 ● DI状态变化自动发送状态数据&#xff0c;可以捕获脉冲 ● 采用Socket自由协议编程简单、轻松应用 ● 开关量毫秒级响应速度适应多种场合 ● 内置网页功能&#xff0c;可以通过网页查询与控制 ● 同时也…

星际争霸之小霸王之小蜜蜂(十二)--猫有九条命

系列文章目录 星际争霸之小霸王之小蜜蜂&#xff08;十一&#xff09;--杀杀杀 星际争霸之小霸王之小蜜蜂&#xff08;十&#xff09;--鼠道 星际争霸之小霸王之小蜜蜂&#xff08;九&#xff09;--狂鼠之灾 星际争霸之小霸王之小蜜蜂&#xff08;八&#xff09;--蓝皮鼠和大…

练习接口测试详细步骤

最近一段时间学了Python语言&#xff0c;重新学了 Java&#xff0c;js&#xff0c;html语言&#xff0c;CSS&#xff0c;linux&#xff0c;一堆测试工具&#xff1b;唉&#xff5e; 在接触接口测试过程中补了很多课&#xff0c; 终于有点领悟接口测试的根本&#xff1b; 偶是…

Beats:安装及配置 Metricbeat (二)- 8.x

这篇文章是继文章 “Beats&#xff1a;安装及配置 Metricbeat &#xff08;一&#xff09;- 8.x” 的续篇。你可以先阅读之前的那篇文章再继续阅读这篇文章。我们在这篇文章中继续之前的探讨。 使用 fingerprint 来代替证书 在实际的使用中&#xff0c;我们需要从 Elasticsear…

SB树,看这一篇就够了

算法拾遗三十九SB树及跳表 SB树SB树四种违规类型总结 SB树Code 跳表 SB树 SB树是基于搜索二叉树来的&#xff0c;也有左旋和右旋的操作&#xff0c;只是不同于AVL树&#xff0c;它也有它自己的一套平衡性方法。 任何以叔叔节点为头的子树的节点个数不小于自己任何一个侄子树的…

如何自己开发一个前端监控SDK

最近在负责团队前端监控系统搭建的任务。因为我们公司有统一的日志存储平台、日志清洗平台和基于 Grafana 搭建的可视化看板&#xff0c;就剩日志的采集和上报需要自己实现了&#xff0c;所以决定封装一个前端监控 SDK 来完成日志的采集和上报。 架构设计 因为想着以后有机会…