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

09.责任链模式

09. 责任链模式

什么是责任链设计模式?

责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的是解耦请求的发送者和接收者,使得多个对象都有机会处理请求,从而增强了系统的灵活性。

责任链模式通常包含以下几个角色:

  1. 请求者(Client):发起请求的对象。
  2. 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个指向下一个处理者的引用。
  3. 具体处理者(Concrete Handler):实现抽象处理者接口的具体类,负责处理它所负责的请求,并决定是否将请求传递给链中的下一个处理者。
  4. 链(Chain):包含多个处理者对象,负责将请求沿着链传递。

责任链模式的工作原理如下:

  • 请求者创建一个请求并将其发送给链的起始处理者。

  • 每个处理者对象检查请求是否由自己处理。

    • 如果能够处理,则处理请求并结束责任链。
    • 如果不能处理,则将请求传递给链中的下一个处理者。
  • 这个过程一直持续,直到请求被处理或传递到链的末端。

责任链模式的优点包括:

  • 增强了系统的灵活性和可扩展性,因为可以动态地添加或移除处理者。
  • 降低了对象之间的耦合度,因为发送者和接收者不需要直接交互。
  • 允许多个对象处理同一个请求,增加了处理请求的灵活性。

责任链模式的缺点包括:

  • 请求的传递路径可能难以跟踪,尤其是在链很长或处理者逻辑复杂的情况下。
  • 责任链可能会导致系统性能问题,因为请求需要在多个对象之间传递。

责任链模式在实际应用中非常广泛,例如在GUI编程中处理事件、在网络编程中处理请求、在工作流系统中处理任务等场景。

举个简单的需求:

假如我们有个登录的场景,在登录处理流程中,需要校验参数、填充参数、登录判断、登录日志记录。我们每步都是环环相扣,此时就可以使用责任链模式。

有几个重要角色:

  • **Action:**责任链中的一个执行动作,主要定义具体执行动作,以及是否需要跳过。
  • **ActionChain:**责任链,用于定义添加执行动作方法,以及调度整条链路动作执行。
  • **ActionContext:**执行动作上下文,定义一个上下文对象,用来在链路执行过程中存储和传输数据。

将上面的步骤看做一个一个执行动作,建立对应的action,使用 chain 将多个 action 进行串联,同时我们可以定义一个context 上下文,用来在各个action之间传输数据。

除此之外,我们也可以通过配置中心,来定义哪些步骤需要执行,哪些可以跳过。

类图如下:

83CCDE77-5B01-46FF-882E-26C9DABFC5AB

代码编写:

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。

A4D6064A-3712-400A-855A-2FB75697DCBC_4_5005_c

所有执行动作正常执行。

(2)测试异常情况, 传输错误的 username 和 password。

D94B82C6-1673-48DA-9493-EED41B96234B_4_5005_c

中间 LoginAction 执行失败,自动跳出责任链,后续执行动作未执行。

到此,一个简单的责任链设计模式的 demo 就已完成。

拓展点:

​ • 可以对接配置中心,动态定义不同业务逻辑中需要执行的动作。

​ • 可以将幂等性校验,添加到判断动作是否执行逻辑中。

相关文章:

09.责任链模式

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

Amazon云计算AWS(一)

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

十_信号4-SIGCHLD信号

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

HCIP的学习(27)

RSTP—802.1W—快速生成树协议 STP缺陷&#xff1a; 1、收敛速度慢----STP的算法是一种被动的算法&#xff0c;依赖于计时器来进行状态变化 2、链路利用率低​ RSTP向下兼容STP协议。&#xff08;STP不兼容RSTP&#xff09; 改进点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文档中图形数据-柱图

效果: 数据替换前 &#xff1a; 替换数据后&#xff1a; 实现代码 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. 首先确保安装了搜狗输入法&#xff0c;且能正常运行。 2.克隆源码到本地。 git clone https://gitcode.com/fcitx/fcitx-qt5.git 3.检查Qt Creator版本&#xff0c;如下图所示&#xff0c;为基于Qt6的。 4. 进入源码目录&#xff0c;建立build文件夹&#xff0c;修改CMak…...

Codeforces 提交Java代码(自己处理输入输出)

示例一&#xff08;A. Watermelon&#xff09; 题目地址 Problem - 4A - Codeforces 题目截图 提交方式 可以提交本地文件&#xff0c;也可以在线提交。我们这里选择在线提交方式&#xff0c;点击上图中的 SUBMIT 按钮&#xff0c;会进入如下界面。 输入Java代码效果如下&a…...

剖析vue中nextTick源码

代码逻辑梳理&#xff1a; callbacks 数组用于存储待执行的回调函数&#xff0c;waiting 变量用于标记是否有待执行的回调函数。 flushCallbacks 函数用于执行所有存储在 callbacks 数组中的回调函数&#xff0c;并在执行完成后将 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++字符串的世界

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C模板入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀STL之string &#x1f4d2;1. string…...

go语言linux安装

下载&#xff1a;https://go.dev/dl/ 命令行使用 wget https://dl.google.com/go/go1.19.3.linux-amd64.tar.gz解压下载的压缩包&#xff0c;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 都是流行的文本编辑器&#xff0c;它们之间有以下主要区别&#xff1a; 历史&#xff1a; vi 是一个非常古老的文本编辑器&#xff0c;最初由 Bill Joy 在 1976 年为 Unix 系统编写。vim&#xff08;Vi IMproved&#xff09;是 vi 的一个增强版&#xff0c;由 Bram M…...

CSS动画效果(鼠标滑过按钮动画)

1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa5SXiaicFfsrcric7TJmGO6YddqC4wFPdM7PGzPHuFgvtDS7MIvnLHB4WFaKia0Qh8VCyUaoyHMc2Zltg/640?wx_fmtgif&fromappmsg&tpwebp&wxfrom5&wx_lazy1&wx_co1 网页设计中的按钮不仅是用户交互的桥梁&#…...

数据结构(C):从初识堆到堆排序的实现

目录 &#x1f31e;0.前言 &#x1f688; 1.堆的概念 &#x1f688; 2.堆的实现 &#x1f69d;2.1堆向下调整算法 &#x1f69d;2.2堆的创建&#xff08;堆向下调整算法&#xff09; ✈️2.2.1 向下调整建堆时间复杂度 &#x1f69d;2.3堆向上调整算法 &#x1f69d;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 硬件环境 最低要求&#xff1a; 为…...

代码随想录35期Day54-JavaScript

Day54题目 ### LeetCode739每日温度 核心思想:今天主要是学会单调栈的使用.找到比元素更大的下一个元素,如果比栈顶元素小就入栈,否则就出栈顶元素,当前元素就是比栈顶元素大的"下一个更大的元素". /*** param {number[]} temperatures* return {number[]}*/ var …...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏

一、引言 在深度学习中&#xff0c;我们训练出的神经网络往往非常庞大&#xff08;比如像 ResNet、YOLOv8、Vision Transformer&#xff09;&#xff0c;虽然精度很高&#xff0c;但“太重”了&#xff0c;运行起来很慢&#xff0c;占用内存大&#xff0c;不适合部署到手机、摄…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

软件工程 期末复习

瀑布模型&#xff1a;计划 螺旋模型&#xff1a;风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合&#xff1a;模块内部功能紧密 模块之间依赖程度小 高内聚&#xff1a;指的是一个模块内部的功能应该紧密相关。换句话说&#xff0c;一个模块应当只实现单一的功能…...