设计模式-职责链模式Chain of Responsibility
职责链模式
- 一、原理和实现
- 二、实现方式
- 1) 使用链表实现
- 2) 使用数组实现
- 3) 扩展
作用:复用和扩展,在实际的项目开发中比较常用。在框架开发中,我们也可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。
一、原理和实现
职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中,它是这么定义的:
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止,实时上,在常见的使用场景中,我们的责任链并不是和概念中的完全一样。
- 原始概念中,是直到链上的某个接收对象能够处理它为止。
- 实际使用中,链上的所有对象都可以对请求进行特殊处理。
二、实现方式
1) 使用链表实现
第一种实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,handle() 是抽象方法。每个具体的处理器类(HandlerA、HandlerB)的 handle() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 successor.handle())。HandlerChain 是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。
public abstract class Handler {// 拥有具体的处理方法,抽象protected Handler nextHandler;public void setNextHandler(Handler nextHandler) {this.nextHandler = nextHandler;}abstract void handle();
}public class HandlerA extends Handler{@Overridepublic void handle() {System.out.println("第一个过滤器");if (nextHandler != null) {nextHandler.handle();}}
}public class HandlerB extends Handler{@Overridepublic void handle() {System.out.println("第二个过滤器");if (nextHandler != null) {nextHandler.handle();}}
}public class HandlerChain {private Handler head = null;private Handler tail = null;public void addHandler(Handler handler) {handler.setSuccessor(null);if (head == null) {head = handler;tail = handler;return;}tail.setSuccessor(handler);tail = handler;}public void handle() {if (head != null) {head.handle();}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
实际上,上面的代码实现不够优雅。处理器类的 handle() 函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的 successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在 handle() 函数中调用 successor.handle(),这就会导致代码出现 bug。
针对这个问题,我们对代码进行重构,利用模板模式,将调用 successor.handle() 的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public final void handle() {boolean handled = doHandle();if (successor != null && !handled) {successor.handle();}}protected abstract boolean doHandle();
}public class HandlerA extends Handler {@Overrideprotected boolean doHandle() {boolean handled = false;//...return handled;}
}public class HandlerB extends Handler {@Overrideprotected boolean doHandle() {boolean handled = false;//...return handled;}
}
// HandlerChain和Application代码不变
2) 使用数组实现
我们再来看第二种实现方式,代码如下所示。这种实现方式更加简单。HandlerChain 类用数组而非链表来保存所有的处理器,并且需要在 HandlerChain 的 handle() 函数中,依次调用每个处理器的 handle() 函数。
public interface IHandler {boolean handle();
}public class HandlerA implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}
public class HandlerB implements IHandler {@Overridepublic boolean handle() {boolean handled = false;//...return handled;}
}public class HandlerChain {private List<IHandler> handlers = new ArrayList<>();public void addHandler(IHandler handler) {this.handlers.add(handler);}public void handle() {for (IHandler handler : handlers) {boolean handled = handler.handle();if (handled) {break;}}}
}
// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
3) 扩展
在 GoF 给出的定义中,**如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。**这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,跟上面的两种实现方式类似,只需要稍微修改即可。
我这里只给出其中一种实现方式,如下所示。另外一种实现方式你对照着上面的实现自行修改。
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public final void handle() {doHandle();if (successor != null) {successor.handle();}}protected abstract void doHandle();
}public class HandlerA extends Handler {@Overrideprotected void doHandle() {//...}
}public class HandlerB extends Handler {@Overrideprotected void doHandle() {//...}
}public class HandlerChain {private Handler head = null;private Handler tail = null;public void addHandler(Handler handler) {handler.setSuccessor(null);if (head == null) {head = handler;tail = handler;return;}tail.setSuccessor(handler);tail = handler;}public void handle() {if (head != null) {head.handle();}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
相关文章:
设计模式-职责链模式Chain of Responsibility
职责链模式 一、原理和实现二、实现方式1) 使用链表实现2) 使用数组实现3) 扩展 作用:复用和扩展,在实际的项目开发中比较常用。在框架开发中,我们也可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下&…...
书生浦语大模型实战营-课程作业(3)
下载sentence_transformer的代码运行情况。sentence_transformer用于embedding(转向量) 本地构建持久化向量数据库。就是把txt和md文件抽取出纯文本,分割成定长(500)后转换成向量,保存到本地,称…...
考研英语单词25
Day 25 bench n.长凳 elastic n.橡皮圈,松紧带 a.灵活的 “e-last 延伸出去” disaster n.灾难,灾祸【disastrous a.灾难性的,极坏的】 deadly a.致命的,极端的,势不两立的 hike n.徒步旅行&…...
计算机网络——08应用层原理
应用层原理 创建一个新的网络 编程 在不同的端系统上运行通过网络基础设施提供的服务,应用进程批次通信如Web Web服务器软件与浏览器软件通信 网络核心中没有应用层软件 网络核心没有应用层功能网络应用只能在端系统上存在 快速网络应用开发和部署 网络应用…...
面试计算机网络框架八股文十问十答第五期
面试计算机网络框架八股文十问十答第五期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新! ⭐点赞⭐收藏⭐不迷路!⭐ 1)与缓存相关的HTTP请…...
拟合案例1:matlab积分函数拟合详细步骤及源码
本文介绍一下基于matlab实现积分函数拟合的过程。采用的工具是lsqcurvefit和nlinfit两个函数工具。关于包含积分运算的函数,这里可以分为两大类啊。我们用具体的案例来展示:一种是积分运算中不包含这个自变量,如下图的第一个公式,也就是说它这个积分运算只有R和Q这两个待定…...
嵌入式软件设计入门:从零开始学习嵌入式软件设计
(本文为简单介绍,个人观点仅供参考) 首先,让我们了解一下嵌入式软件的定义。嵌入式软件是指运行在嵌入式系统中的特定用途软件,它通常被用来控制硬件设备、处理实时数据和实现特定功能。与桌面应用程序相比,嵌入式软件需要具备更高的实时性、…...
Educational Codeforces Round 135 (Rated for Div. 2)C. Digital Logarithm(思维)
文章目录 题目链接题意题解代码 题目链接 C. Digital Logarithm 题意 给两个长度位 n n n的数组 a a a、 b b b,一个操作 f f f 定义操作 f f f为, a [ i ] f ( a [ i ] ) a [ i ] a[i]f(a[i])a[i] a[i]f(a[i])a[i]的位数 求最少多少次操作可以使 …...
微信小程序介绍、账号申请、开发者工具目录结构详解及小程序配置
目录 一、微信小程序介绍 1.什么是小程序? 2.小程序可以干什么? 3.微信小程序特点 二、账号申请 1.账号注册 2.测试号申请 三、安装开发工具 四、开发小程序 五、目录结构 JSON 配置 小程序配置 app.json 工具配置 project.config.json 页…...
数字的魅力之情有独钟的素数
情有独钟的素数 什么是素数 素数(Prime number)也称为质数,是指在非0自然数中,除了1与其本身之外不拥有其他因数的自然数。也就是说,素数需要满足两个条件: 大于1的整数;只拥有1和其自身两个…...
Vue2源码梳理:render函数的实现
render 在 $mount 时,会调用 render 方法在写 template 时,最终也会转换成 render 方法Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node它的定义在 src/core/instance/render.js 文件中,它返回的是一个…...
flask+python企业产品订单管理系统938re
在设计中采用“自下而上”的思想,在创新型产品提前购模块实现了个人中心、个体管理、发布企业管理、投资企业管理、项目分类管理、产品项目管理、个体投资管理、企业投资管理、个体订单管理、企业订单管理、系统管理等的功能性进行操作。最终,对基本系统…...
Vue2源码梳理:关于数据驱动,与new Vue时的初始化操作
数据驱动 1 )概述 vue的一个核心思想,就是数据驱动 所谓数据驱动,就是指视图是由数据驱动生成的 对视图的修改并不会直接操作dom,而是通过修改数据 它相比我们传统的前端开发,如使用 jQuery 的前端库直接去修改 dom…...
【C++航海王:追寻罗杰的编程之路】关于模板,你知道哪些?
目录 1 -> 泛型编程 2 -> 函数模板 2.1 -> 函数模板概念 2.2 -> 函数模板格式 2.3 -> 函数模板的原理 2.4 -> 函数模板的实例化 2.5 -> 函数参数的匹配原则 3 -> 类模板 3.1 -> 类模板的定义格式 3.2 -> 类模板的实例化 1 -> 泛型编…...
分布式springboot 3项目集成mybatis官方生成器开发记录
文章目录 说明实现思路实现步骤第一步:创建generator子模块第二步:引入相关maven插件和依赖第三步:编写生成器配置文件第四步:运行查看结果 说明 该文章为作者开发学习记录,方便以后复习和交流主要内容为:…...
算法学习——LeetCode力扣回溯篇4
算法学习——LeetCode力扣回溯篇4 332. 重新安排行程 332. 重新安排行程 - 力扣(LeetCode) 描述 给你一份航线列表 tickets ,其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票…...
c++ STL系列——(六)multimap
C标准模板库(STL)是C编程中不可或缺的一部分,它提供了一系列的容器、算法和函数模板,以简化常见的数据结构和算法的实现。在STL中,multimap是一个非常有用的容器,它提供了一种键值对的存储方式,…...
Json-序列化字符串时间格式问题
序列化字符串时间格式问题 一、项目场景二、问题描述三、解决方案 一、项目场景 最近C#中需要将实体进行json序列化,使用了Newtonsoft.Json public static void TestJson(){DataTable dt new DataTable();dt.Columns.Add("Age", Type.GetType("Sys…...
HarmonyOS鸿蒙学习基础篇 - 自定义组件(一)
前言 在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离&#…...
开窗,挖槽,放电齿,拼版
我们在阻焊层画线,就相当于去掉绿油阻焊,开窗一般是用在大电流走线的时候。先画要走的导线,之后切换到TopSolder或者Bottom Solder层,然后Place->line 画一条和原来先粗细一样的线即可!但走电流的仍然是导线&#x…...
BERT 模型:自然语言处理的新篇章
BERT模型:自然语言处理的新篇章 在人工智能领域,自然语言处理(NLP)一直是研究的热点之一。2018年,谷歌推出的BERT模型彻底改变了NLP的发展方向,成为该领域的重要里程碑。BERT(Bidirectional En…...
告别抢票焦虑:用Python自动化脚本轻松获取大麦网演唱会门票
告别抢票焦虑:用Python自动化脚本轻松获取大麦网演唱会门票 【免费下载链接】DamaiHelper 大麦网演唱会演出抢票脚本。 项目地址: https://gitcode.com/gh_mirrors/dama/DamaiHelper 还在为心仪的演唱会门票秒光而烦恼吗?DamaiHelper大麦网抢票脚…...
新手福音:利用快马一键生成mobaxterm中文界面配置脚本
作为一个经常需要远程连接服务器的用户,MobaXterm一直是我的主力工具之一。但刚开始使用时,全英文的界面确实让我这个新手有点手足无措。最近发现用InsCode(快马)平台可以快速生成配置脚本,简直不要太方便! 为什么需要中文界面 对…...
从nvidia-smi到npu-smi:给CUDA开发者的华为昇腾NPU监控指南
从nvidia-smi到npu-smi:CUDA开发者快速掌握昇腾NPU监控的实战手册 当你的技术栈从英伟达GPU扩展到华为昇腾NPU时,监控工具的使用体验就像从自动挡切换到手动挡——虽然最终目的地相同,但操作逻辑需要重新适应。作为曾经每天与nvidia-smi打交道…...
Cadence 617实战:手把手教你搞定电流镜负载差分放大器的仿真与优化
Cadence 617实战:手把手教你搞定电流镜负载差分放大器的仿真与优化 在模拟集成电路设计中,电流镜负载差分放大器是一个经典而重要的电路结构。它不仅出现在各类运算放大器的输入级,也是理解模拟电路设计原理的绝佳案例。本文将带你从工具实操…...
OpenClaw跨平台对比:nanobot在Mac/Win/Linux的表现差异
OpenClaw跨平台对比:nanobot在Mac/Win/Linux的表现差异 1. 测试背景与实验设计 去年夏天我开始尝试用OpenClaw搭建个人自动化工作流时,发现不同操作系统下的表现差异远超预期。这次我选择了基于Qwen3-4B模型的nanobot镜像,在MacBook Pro M1…...
告别命令行恐惧:用乐鑫官方Flash Download Tool图形化烧录ESP32-S3固件(保姆级图文教程)
告别命令行恐惧:乐鑫Flash Download Tool图形化烧录ESP32-S3全指南 第一次接触ESP32开发板时,那个闪烁的命令行窗口让我手足无措。直到发现乐鑫官方的Flash Download Tool,才发现原来固件烧录可以如此直观简单——不需要记忆任何命令参数&…...
中文AI象年轻小伙与英语AI象老年人:一场算力背后的文明时差
中文AI与英语AI:一场算力背后的文明时差当AI算力的齿轮飞速运转,中文AI与英语AI的差距早已超越“风格不同”,成为算力效率、表达质感与发展潜力的全方位断层——中文AI如意气风发的年轻小伙,灵动通透、反应敏捷,以极低…...
告别信号盲猜!手把手教你用ESP32的Scan和iperf给家里WiFi做个‘体检’
用ESP32打造家庭WiFi信号检测仪:从扫描到吞吐测试全攻略 你是否遇到过在家追剧时视频卡顿、智能设备频繁掉线的困扰?墙角的摄像头总是连接不稳定,书房里的温湿度传感器数据时有时无——这些问题很可能与WiFi信号覆盖不均有关。今天我们将利用…...
从理论到实践:双有源桥DAB-SPS控制模式仿真全解析
1. 双有源桥DAB与SPS控制模式入门 第一次接触双有源桥(Dual Active Bridge,简称DAB)时,我被它优雅的对称结构吸引住了。这种DC-DC变换器拓扑就像一座精心设计的桥梁,两侧各有一个全桥电路,通过高频变压器耦…...
