09.责任链模式
09. 责任链模式
什么是责任链设计模式?
责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的是解耦请求的发送者和接收者,使得多个对象都有机会处理请求,从而增强了系统的灵活性。
责任链模式通常包含以下几个角色:
- 请求者(Client):发起请求的对象。
- 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个指向下一个处理者的引用。
- 具体处理者(Concrete Handler):实现抽象处理者接口的具体类,负责处理它所负责的请求,并决定是否将请求传递给链中的下一个处理者。
- 链(Chain):包含多个处理者对象,负责将请求沿着链传递。
责任链模式的工作原理如下:
-
请求者创建一个请求并将其发送给链的起始处理者。
-
每个处理者对象检查请求是否由自己处理。
-
- 如果能够处理,则处理请求并结束责任链。
- 如果不能处理,则将请求传递给链中的下一个处理者。
-
这个过程一直持续,直到请求被处理或传递到链的末端。
责任链模式的优点包括:
- 增强了系统的灵活性和可扩展性,因为可以动态地添加或移除处理者。
- 降低了对象之间的耦合度,因为发送者和接收者不需要直接交互。
- 允许多个对象处理同一个请求,增加了处理请求的灵活性。
责任链模式的缺点包括:
- 请求的传递路径可能难以跟踪,尤其是在链很长或处理者逻辑复杂的情况下。
- 责任链可能会导致系统性能问题,因为请求需要在多个对象之间传递。
责任链模式在实际应用中非常广泛,例如在GUI编程中处理事件、在网络编程中处理请求、在工作流系统中处理任务等场景。
举个简单的需求:
假如我们有个登录的场景,在登录处理流程中,需要校验参数、填充参数、登录判断、登录日志记录。我们每步都是环环相扣,此时就可以使用责任链模式。
有几个重要角色:
- **Action:**责任链中的一个执行动作,主要定义具体执行动作,以及是否需要跳过。
- **ActionChain:**责任链,用于定义添加执行动作方法,以及调度整条链路动作执行。
- **ActionContext:**执行动作上下文,定义一个上下文对象,用来在链路执行过程中存储和传输数据。
将上面的步骤看做一个一个执行动作,建立对应的action,使用 chain 将多个 action 进行串联,同时我们可以定义一个context 上下文,用来在各个action之间传输数据。
除此之外,我们也可以通过配置中心,来定义哪些步骤需要执行,哪些可以跳过。
类图如下:
代码编写:
1、定义顶级接口
(1)定义责任链执行动作上下文抽象类,用于责任链上下文之间数据传输。
@Data
public abstract class ActionContext implements Serializable, Cloneable {private static final long serialVersionUID = 1L;/*** 执行链名称,用于获取配置*/private String actionChainName;/*** 跳到结果处理*/private boolean isSkipToResult = false;public Object clone() throws CloneNotSupportedException {return super.clone();}
}
(2)定义责任链执行动作基类
public interface IAction<T extends ActionContext> {/*** 是否需要跳过* @param context 上下文* @return true/false*/default boolean isSkippered(T context) throws Exception{if (context.isSkipToResult()) {return true;}// 通过配置中心获取是否需要执行List<String> config = ConfigServer.getConfig(context.getActionChainName());if (config.contains(getName())) {return false;}return true;}/*** 执行* @param context 上下文*/void execute(T context) throws Exception;/*** 获取执行动作名称,用于和配置中心进行匹配* @return*/String getName();}
(3)定义Action 执行链接口
public interface IActionChain<T extends ActionContext> {/*** 添加一个Action* @param action 上下文* @return action链*/IActionChain<T> appendAction(Class<? extends IAction<T>> action);IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions);IActionChain<T> appendAction(IAction<T> action);/*** 执行动作* @param context 上下文*/void execute(T context) throws Exception;
}
2、实现接口,定义具体的执行
(1)登录上下文
/*** 登录上下文*/
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginActionContext extends ActionContext {/*** 失败日志*/private String failMsg;private String userName;private String password;private String token;private String ip;private String device;private Boolean isLoginFlag = true;
}
(2)定义责任链通用模版类
public class RouteActionChain<T extends ActionContext> implements IActionChain<T> {private List<IAction<T>> actionChain = new ArrayList<IAction<T>>();@Overridepublic IActionChain<T> appendAction(Class<? extends IAction<T>> action) {actionChain.add(getActionInstance(action));return this;}@Overridepublic IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions) {if (CollectionUtils.isEmpty(actions)) {return this;}for (Class<? extends IAction<T>> clazz : actions) {actionChain.add(getActionInstance(clazz));}return this;}@Overridepublic IActionChain<T> appendAction(IAction<T> action) {actionChain.add(action);return this;}@Overridepublic void execute(T context) throws Exception {for (IAction<T> action : actionChain) {//如果跳过 就不需要继续执行,这里顺序不能改变if (action.isSkippered(context)) {continue;}action.execute(context);}}public static <T extends ActionContext> IAction<T> getActionInstance(Class<? extends IAction<T>> clazz) {Collection<? extends IAction<T>> s = BeanUtil.getBeans(clazz);if (s != null && s.size() == 1) {return s.iterator().next();} else {throw new RuntimeException("action is not found");}}
}
(3)定义执行动作
- 校验参数执行动作
@Slf4j@Componentpublic class CheckParamAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) {// do somethinglog.info("CheckParamAction execute......");// 使用断言,判断用户名不为空try {Assert.isTrue(!StringUtils.isEmpty(context.getUserName()), "用户名不能为空");Assert.isTrue(!StringUtils.isEmpty(context.getPassword()), "密码不能为空");} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "CheckParamAction";}}
填充参数执行动作
@Slf4j@Componentpublic class FullParamAction implements IAction<LoginActionContext> {@Autowiredprivate ConfigServer configServer;@Overridepublic void execute(LoginActionContext context) throws Exception {log.info("FullParamAction execute......");// 使用断言,判断用户名不为空try {// do somethingcontext.setIp("127.0.0.1");context.setDevice("PC");context.setToken("123456");} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "FullParamAction";}}
- 登录判断执行动作
@Slf4j@Componentpublic class LoginAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) throws Exception {// 模拟登录log.info("LoginAction execute......");try {// do somethingif(context.getUserName().equals(context.getPassword())) {context.setIsLoginFlag(true);} else {context.setIsLoginFlag(false);context.setFailMsg("用户名或密码输入错误");}} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "LoginAction";}}
- 登录日志记录执行动作
@Slf4j
@Component
public class LogAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) throws Exception {log.info("FullParamAction execute......");// 使用断言,判断用户名不为空try {// do somethinglog.info("数据库插入登录日志:{}", JSONObject.toJSONString(context));} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "LogAction";}
}
(4)模拟配置中心
配置需要的执行动作,没有配置的自动跳过
@Component
@Data
public class ConfigServer {private static Map<String, List<String>> configMap = new HashMap<>();@PostConstructpublic void init(){ArrayList<String> configList = new ArrayList<>();configList.add("CheckParamAction");configList.add("FullParamAction");configList.add("LogAction");configList.add("LoginAction");configMap.put("login", configList);}/*** 获取配置列表* @param actionChainName* @return*/public static List<String> getConfig(String actionChainName) {return configMap.getOrDefault(actionChainName, new ArrayList<>());}
}
3、测试
定义测试接口
@Service
public class LoginServiceImpl implements LoginService {@Overridepublic boolean login(String userName, String password) {// 创建上下文LoginActionContext loginActionContext = new LoginActionContext();loginActionContext.setActionChainName("login");loginActionContext.setUserName(userName);loginActionContext.setPassword(password);// 构建责任链RouteActionChain<LoginActionContext> chain = new RouteActionChain<>();chain.appendAction(CheckParamAction.class);chain.appendAction(FullParamAction.class);chain.appendAction(LoginAction.class);chain.appendAction(LogAction.class);try {chain.execute(loginActionContext);} catch (Exception e) {throw new RuntimeException(e);}return loginActionContext.getIsLoginFlag();}
}
(1)测试正常情况,传输正确的 username 和 password。
所有执行动作正常执行。
(2)测试异常情况, 传输错误的 username 和 password。
中间 LoginAction 执行失败,自动跳出责任链,后续执行动作未执行。
到此,一个简单的责任链设计模式的 demo 就已完成。
拓展点:
• 可以对接配置中心,动态定义不同业务逻辑中需要执行的动作。
• 可以将幂等性校验,添加到判断动作是否执行逻辑中。
相关文章:

09.责任链模式
09. 责任链模式 什么是责任链设计模式? 责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的…...

Amazon云计算AWS(一)
目录 一、基础存储架构Dynamo(一)Dynamo概况(二)Dynamo架构的主要技术 二、弹性计算云EC2(一)EC2的基本架构(二)EC2的关键技术(三)EC2的安全及容错机制 提供的…...

十_信号4-SIGCHLD信号
SIGCHLD信号 在学习进程控制的时候,使用wait和waitpid系统调用何以回收僵尸进程,父进程可以阻塞等待,也可以非阻塞等待,采用轮询的方式不停查询子进程是否退出。 采用阻塞式等待,父进程就被阻塞了,什么都干…...

HCIP的学习(27)
RSTP—802.1W—快速生成树协议 STP缺陷: 1、收敛速度慢----STP的算法是一种被动的算法,依赖于计时器来进行状态变化 2、链路利用率低 RSTP向下兼容STP协议。(STP不兼容RSTP) 改进点1—端口角色 802.1D协议---根端口、指定端口…...

6. MySQL 查询、去重、别名
文章目录 【 1. 数据表查询 SELECT 】1.1 查询表中所有字段使用 * 查询表的所有字段列出表的所有字段 1.2 查询表中指定的字段 【 2. 去重 DISTINCT 】【 3. 设置别名 AS 】3.1 为表指定别名3.2 为字段指定别名 【 5. 限制查询结果的条数 LIMIT 】5.1 指定初始位置5.2 不指定初…...

Oracle导出clob字段到csv
使用UTL_FILE ref: How to Export The Table with a CLOB Column Into a CSV File using UTL_FILE ?(Doc ID 1967617.1) --preapre data CREATE TABLE TESTCLOB(ID NUMBER, MYCLOB1 CLOB, MYCLOB2 CLOB ); INSERT INTO TESTCLOB(ID,MYCLOB1,MYCLOB2) VALUES(1,Sample row 11…...
C++无锁(lock free)队列moodycamel::ConcurrentQueue
moodycamel::ConcurrentQueue介绍 moodycamel::ConcurrentQueue一个用C++11实现的多生产者、多消费者无锁队列。 它具有以下特点: 1.快的让人大吃一惊,详见不同无锁队列之间的压测对比 2.单头文件实现,很容易集成到你的项目中 3.完全线程安全的无锁队列,支持任意线程数的并…...

python办公自动化——(二)替换PPT文档中图形数据-柱图
效果: 数据替换前 : 替换数据后: 实现代码 import collections.abc from pptx import Presentation from pptx.util import Cm,Pt import pyodbc import pandas as pd from pptx.chart.data impo…...

vue不同页面切换的方式(Vue动态组件)
v-if实现 <!--Calender.vue--> <template><a-calendar v-model:value"value" panelChange"onPanelChange" /></template> <script setup> import { ref } from vue; const value ref(); const onPanelChange (value, mod…...

Linux下Qt Creator无法输入中文(已解决)
1. 首先确保安装了搜狗输入法,且能正常运行。 2.克隆源码到本地。 git clone https://gitcode.com/fcitx/fcitx-qt5.git 3.检查Qt Creator版本,如下图所示,为基于Qt6的。 4. 进入源码目录,建立build文件夹,修改CMak…...

Codeforces 提交Java代码(自己处理输入输出)
示例一(A. Watermelon) 题目地址 Problem - 4A - Codeforces 题目截图 提交方式 可以提交本地文件,也可以在线提交。我们这里选择在线提交方式,点击上图中的 SUBMIT 按钮,会进入如下界面。 输入Java代码效果如下&a…...
剖析vue中nextTick源码
代码逻辑梳理: callbacks 数组用于存储待执行的回调函数,waiting 变量用于标记是否有待执行的回调函数。 flushCallbacks 函数用于执行所有存储在 callbacks 数组中的回调函数,并在执行完成后将 waiting 设置为 false。 timer 函数根据环境…...

SSM牙科诊所管理系统-计算机毕业设计源码98077
目 录 摘要 1 绪论 1.1研究目的与意义 1.2国内外研究现状 1.3ssm框架介绍 1.4论文结构与章节安排 2 牙科诊所管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能…...

【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C “ 登神长阶 ” 🤡往期回顾🤡:C模板入门 🌹🌹期待您的关注 🌹🌹 ❀STL之string 📒1. string…...

go语言linux安装
下载:https://go.dev/dl/ 命令行使用 wget https://dl.google.com/go/go1.19.3.linux-amd64.tar.gz解压下载的压缩包,linux建议放在/opt目录下 我放在/home/ihan/go_sdk下 sudo tar -C /home/ihan/go_sdk -xzf go1.19.3.linux-amd64.tar.gz 这里的参数…...
vi和vim有什么不同?
vi 和 vim 都是流行的文本编辑器,它们之间有以下主要区别: 历史: vi 是一个非常古老的文本编辑器,最初由 Bill Joy 在 1976 年为 Unix 系统编写。vim(Vi IMproved)是 vi 的一个增强版,由 Bram M…...
CSS动画效果(鼠标滑过按钮动画)
1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa5SXiaicFfsrcric7TJmGO6YddqC4wFPdM7PGzPHuFgvtDS7MIvnLHB4WFaKia0Qh8VCyUaoyHMc2Zltg/640?wx_fmtgif&fromappmsg&tpwebp&wxfrom5&wx_lazy1&wx_co1 网页设计中的按钮不仅是用户交互的桥梁&#…...

数据结构(C):从初识堆到堆排序的实现
目录 🌞0.前言 🚈 1.堆的概念 🚈 2.堆的实现 🚝2.1堆向下调整算法 🚝2.2堆的创建(堆向下调整算法) ✈️2.2.1 向下调整建堆时间复杂度 🚝2.3堆向上调整算法 🚝2.…...

ChatGLM3-6B部署
ZhipuAI/chatglm3-6b 模型文件地址 chatglm3-6B-32k-int4 量化的模型地址 ChatGLM3 代码仓库 ChatGLM3 技术文档 cpolar http xxx 端口 /anaconda3/envs/chatglm2/lib/python3.8/site-packages/gradio$ networking.py 硬件环境 最低要求: 为…...
代码随想录35期Day54-JavaScript
Day54题目 ### LeetCode739每日温度 核心思想:今天主要是学会单调栈的使用.找到比元素更大的下一个元素,如果比栈顶元素小就入栈,否则就出栈顶元素,当前元素就是比栈顶元素大的"下一个更大的元素". /*** param {number[]} temperatures* return {number[]}*/ var …...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...