责任链模式:构建灵活可扩展的请求处理体系(Java 实现详解)

一、责任链模式核心概念解析

(一)模式定义与本质

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,其核心思想是将多个处理者对象连成一条链,并沿着这条链传递请求,直到有某个处理者对象处理它为止。这种模式通过将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,从而避免请求发送者与具体处理者之间的紧耦合。

责任链模式的本质可以概括为:传递请求,推卸责任。每个处理者只负责处理自己职责范围内的请求,对于超出职责范围的请求,则将其传递给链中的下一个处理者,形成一个灵活请求处理链条。

(二)核心应用场景

  1. 多级审批流程:如请假审批、费用报销审批等,不同级别的审批者处理不同额度或类型的请求。
  2. 过滤链场景:例如日志处理中的多级过滤、HTTP 请求的过滤器链。
  3. 错误处理链:在复杂系统中,不同类型的错误由不同的处理器进行处理。
  4. 事件处理机制:图形界面开发中,事件可能需要多个组件依次处理,如按钮点击事件可能先由按钮处理,再传递给容器组件。

(三)模式优缺点分析

优点

  • 解耦请求发送者与处理者:发送者无需知道具体哪个处理者处理请求,只需将请求发送到链上。
  • 动态灵活:可以在运行时动态调整责任链的顺序,新增或删除处理者,而无需修改客户端代码。
  • 符合开闭原则:新增处理者只需实现接口并加入链中,不影响现有代码。

缺点

  • 请求可能未被处理:如果责任链中没有处理者能够处理该请求,可能导致请求丢失,需要在链尾设置默认处理者。
  • 调试难度增加:请求的处理路径可能较长,调试时需要跟踪整个链的处理过程。
  • 性能开销:每个请求都需要沿着链进行传递,可能会带来一定的性能损失,尤其是在链较长时。

二、责任链模式的核心角色与 UML 结构

(一)四大核心角色

  1. 抽象处理者(Handler):定义处理请求的接口,包含一个指向后继处理者的引用,并实现默认的请求传递方法(如设置下一个处理者、传递请求等)。
  2. 具体处理者(Concrete Handler):实现抽象处理者定义的处理方法,判断是否能够处理当前请求,若能则处理,否则将请求传递给后继处理者。
  3. 请求对象(Request):封装请求的相关信息,供处理者判断是否需要处理。
  4. 客户端(Client):创建责任链,并向链的头部发送请求。

(二)UML 类图结构

plantuml

@startuml
class Client{- Handler headHandler+ void sendRequest(Request request)
}
class Handler{- Handler nextHandler+ setNextHandler(Handler handler)+ handleRequest(Request request)
}
class ConcreteHandler1{+ handleRequest(Request request)
}
class ConcreteHandler2{+ handleRequest(Request request)
}
class Request{- Object data// getters and setters
}
Client --> Handler
Handler <|-- ConcreteHandler1
Handler <|-- ConcreteHandler2
Handler "1" -- "0..1" Handler : nextHandler
@enduml

(三)关键交互流程

  1. 客户端创建具体处理者实例,并通过setNextHandler方法将处理者连接成链,确定链的顺序。
  2. 客户端向链的头部处理者(通常是第一个处理者)发送请求。
  3. 头部处理者接收到请求后,先判断自己是否能够处理该请求:
    • 若能处理,则执行具体的处理逻辑,处理完毕后根据需要决定是否继续传递请求(通常处理后不再传递)。
    • 若不能处理,则调用nextHandlerhandleRequest方法,将请求传递给下一个处理者。
  4. 后续处理者重复上述步骤,直到请求被处理或到达链尾。

三、Java 手写责任链模式实现 —— 请假审批系统

(一)业务场景说明

假设我们需要实现一个员工请假审批系统,请假流程如下:

  • 请假天数≤1 天,由直属领导审批。
  • 1 天 < 请假天数≤3 天,由部门经理审批。
  • 3 天 < 请假天数≤7 天,由总监审批。
  • 请假天数 > 7 天,由总经理审批。

(二)实现步骤详解

1. 定义请求类(Request)

java

public class LeaveRequest {private String employeeName; // 员工姓名private int days; // 请假天数private String reason; // 请假原因public LeaveRequest(String employeeName, int days, String reason) {this.employeeName = employeeName;this.days = days;this.reason = reason;}// getters and setterspublic String getEmployeeName() { return employeeName; }public int getDays() { return days; }public String getReason() { return reason; }
}
2. 定义抽象处理者(Handler)

java

public abstract class Approver {protected Approver nextApprover; // 后继处理者// 设置下一个处理者public void setNextApprover(Approver nextApprover) {this.nextApprover = nextApprover;}// 处理请求的抽象方法public abstract void processRequest(LeaveRequest request);
}
3. 实现具体处理者(Concrete Handler)

直属领导(TeamLeaderApprover)

java

public class TeamLeaderApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() <= 1) {System.out.println("直属领导" + this + "审批通过:" + request.getEmployeeName() + "请假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request); // 传递给下一个处理者} else {System.out.println("请假天数过长,无人审批!");}}}
}

部门经理(DepartmentManagerApprover)

java

public class DepartmentManagerApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 1 && request.getDays() <= 3) {System.out.println("部门经理" + this + "审批通过:" + request.getEmployeeName() + "请假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("请假天数过长,无人审批!");}}}
}

总监(DirectorApprover)

java

public class DirectorApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 3 && request.getDays() <= 7) {System.out.println("总监" + this + "审批通过:" + request.getEmployeeName() + "请假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("请假天数过长,无人审批!");}}}
}

总经理(GeneralManagerApprover)

java

public class GeneralManagerApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 7) {System.out.println("总经理" + this + "审批通过:" + request.getEmployeeName() + "请假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("请假天数过长,无人审批!");}}}
}
4. 客户端调用(Client)

java

public class Client {public static void main(String[] args) {// 创建处理者实例Approver teamLeader = new TeamLeaderApprover();Approver departmentManager = new DepartmentManagerApprover();Approver director = new DirectorApprover();Approver generalManager = new GeneralManagerApprover();// 构建责任链teamLeader.setNextApprover(departmentManager);departmentManager.setNextApprover(director);director.setNextApprover(generalManager);// 创建请求LeaveRequest request1 = new LeaveRequest("张三", 1, "病假");LeaveRequest request2 = new LeaveRequest("李四", 2, "事假");LeaveRequest request3 = new LeaveRequest("王五", 5, "婚假");LeaveRequest request4 = new LeaveRequest("赵六", 10, "产假");// 发送请求teamLeader.processRequest(request1);teamLeader.processRequest(request2);teamLeader.processRequest(request3);teamLeader.processRequest(request4);}
}
5. 运行结果

plaintext

直属领导TeamLeaderApprover@1540e19d审批通过:张三请假1天,原因:病假
部门经理DepartmentManagerApprover@677327b6审批通过:李四请假2天,原因:事假
总监DirectorApprover@14ae5a5审批通过:王五请假5天,原因:婚假
总经理GeneralManagerApprover@7f31245a审批通过:赵六请假10天,原因:产假

(三)实现关键点解析

  1. 处理者链的构建:通过setNextApprover方法将处理者依次连接,形成责任链。链的顺序非常重要,决定了请求的处理顺序,通常按照处理者的处理能力从小到大或从低到高排列。
  2. 请求处理逻辑:每个具体处理者首先判断自己是否能处理当前请求,处理逻辑可以是基于请求的属性(如请假天数)、权限等。若不能处理,则传递给下一个处理者,注意要处理nextApprovernull的情况(链尾),避免空指针异常。
  3. 抽象处理者的设计:抽象类中定义了后继处理者的引用和设置方法,以及处理请求的抽象方法,确保所有具体处理者具有一致的接口。

四、责任链模式的优化与扩展

(一)带返回值的责任链

在上述案例中,处理者处理请求后通常不会返回结果,但在实际场景中,可能需要处理者返回处理结果(如审批是否通过、处理后的数据等)。此时可以修改processRequest方法,使其返回一个结果对象。

修改抽象处理者

java

public abstract class Approver {// ... 其他代码不变public abstract ApproveResult processRequest(LeaveRequest request);
}// 审批结果类
public class ApproveResult {private boolean approved;private String message;// 构造方法、getters and setters
}

具体处理者返回结果

java

public class TeamLeaderApprover extends Approver {@Overridepublic ApproveResult processRequest(LeaveRequest request) {ApproveResult result = new ApproveResult();if (request.getDays() <= 1) {result.setApproved(true);result.setMessage("直属领导审批通过");} else {if (nextApprover != null) {result = nextApprover.processRequest(request); // 传递请求并获取结果} else {result.setApproved(false);result.setMessage("无人审批");}}return result;}
}

(二)动态构建责任链

在客户端硬编码责任链的顺序不够灵活,特别是当链的结构可能频繁变化时。可以通过配置文件(如 XML、JSON)或数据库来存储处理者的顺序,运行时动态加载并构建责任链。

示例:从配置文件加载责任链

  1. 创建配置文件approvers.config

properties

approver1=com.example.TeamLeaderApprover
approver2=com.example.DepartmentManagerApprover
approver3=com.example.DirectorApprover
approver4=com.example.GeneralManagerApprover
  1. 客户端动态加载处理者并构建链:

java

public class Client {public static void main(String[] args) throws Exception {Properties properties = new Properties();properties.load(new FileInputStream("approvers.config"));List<Approver> approvers = new ArrayList<>();for (int i = 1; i <= properties.size(); i++) {String className = properties.getProperty("approver" + i);Class<?> clazz = Class.forName(className);Approver approver = (Approver) clazz.newInstance();approvers.add(approver);}// 构建责任链for (int i = 0; i < approvers.size() - 1; i++) {approvers.get(i).setNextApprover(approvers.get(i + 1));}// 发送请求...}
}

(三)使用 Spring 实现责任链(依赖注入方式)

在 Spring 框架中,可以利用依赖注入来管理处理者实例,并通过 AOP 或自定义注解来简化责任链的构建。

步骤如下

  1. 定义处理者接口和抽象类(同前文)。
  2. 将具体处理者声明为 Spring Bean:

java

@Component
public class TeamLeaderApprover extends Approver {// ... 处理逻辑
}
  1. 使用@Autowired注入所有处理者,并按照顺序构建链:

java

@Service
public class ApproveService {private List<Approver> approvers;@Autowiredpublic ApproveService(List<Approver> approvers) {// 假设approvers已按顺序注入,可能需要通过@Order注解排序for (int i = 0; i < approvers.size() - 1; i++) {approvers.get(i).setNextApprover(approvers.get(i + 1));}}public void approve(LeaveRequest request) {approvers.get(0).processRequest(request); // 从链头开始处理}
}

五、责任链模式与其他设计模式的对比

(一)vs 策略模式(Strategy Pattern)

对比维度责任链模式策略模式
目的处理请求链,一个请求可能被多个处理者处理封装不同算法策略,选择其中一种算法处理请求
结构处理者之间形成链式结构,请求沿链传递策略类之间是平行的,客户端直接选择具体策略
交互方式处理者自动传递请求,无需客户端干预客户端主动选择具体策略并调用
适用场景请求需要按顺序经过多个处理者处理需要动态切换不同算法实现

(二)vs 状态模式(State Pattern)

对比维度责任链模式状态模式
核心思想传递请求,多个处理者可能处理同一个请求根据对象状态变化改变行为,状态之间自动切换
处理者关系处理者之间是链式的,无状态关联状态对象之间通常是互斥的,对象当前状态决定行为
请求处理请求可能被多个处理者处理(取决于链的顺序)请求由当前状态对象处理,一个请求对应一个状态处理

(三)vs 观察者模式(Observer Pattern)

对比维度责任链模式观察者模式
通信方向请求由发送者向处理者单向传递主题与观察者之间是双向通信(主题通知观察者)
处理方式处理者按顺序处理请求,通常只有一个处理者处理多个观察者可以同时响应主题的变化
解耦程度发送者与处理者解耦,但处理者之间有链式关联主题与观察者解耦,观察者之间无关联

六、最佳实践与注意事项

(一)链的长度控制

避免责任链过长,过长的链会导致请求处理效率低下,且调试困难。可以通过日志记录链的处理过程,或者在链中设置最大处理深度限制。

(二)链尾处理

必须确保责任链有一个最终处理者(如默认处理者),避免请求未被处理的情况。例如在请假审批系统中,总经理作为链尾处理者,处理所有超过 7 天的请假请求。

(三)处理者的单一职责

每个处理者应专注于处理特定类型的请求,遵循单一职责原则,避免处理者承担过多职责,导致逻辑复杂。

(四)日志与调试

在处理者中加入日志记录,记录请求的处理过程,方便调试和问题排查。例如记录请求进入处理者的时间、处理结果、传递给下一个处理者的时间等。

(五)性能优化

如果责任链中的处理者较多,且大部分请求需要传递到链尾才能处理,可以考虑使用缓存或预处理机制,提前判断请求应该由哪个处理者处理,避免逐个传递请求。

七、总结与拓展

责任链模式通过将处理者连成链条,实现了请求处理的解耦和灵活扩展,是处理多级流程、过滤链等场景的理想选择。在 Java 开发中,我们可以通过抽象类和接口定义处理者,通过组合模式构建责任链,结合 Spring 等框架实现更高效的管理。

随着微服务架构的普及,责任链模式的思想也被应用到分布式系统中,如请求拦截链、网关过滤器链等。深入理解责任链模式的核心原理,能够帮助我们更好地设计可扩展的系统架构,应对复杂的业务需求变化。

在实际项目中,是否选择责任链模式需要根据具体场景权衡,考虑请求处理的复杂度、处理者的动态性以及系统的可维护性等因素。通过合理应用责任链模式,我们可以构建出更加灵活、健壮的软件系统。

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

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

相关文章

如何进行页面前端监控

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 前端监控主要分三个方向 前端性能&#xff08;用户体验优化&#xff09; 异常监控 业务指标跟 下面我来分别介绍三类指标如何获取 1&#xff09;前端性能指标&#xff1a; …

Ajax技术分析方法全解:从基础到企业级实践(2025最新版)

引言 Ajax技术自2005年正式命名以来,已支撑全球83%的Web应用实现异步交互。2025年最新数据显示,单页面应用(SPA)的Ajax请求密度已达日均120亿次/应用。本文将系统化解析Ajax分析方法论,涵盖从基础原理到企业级工程实践的完整技术栈。 一、Ajax技术架构解构 1.1 核心组件…

git管理github上的repository

1. 首先注册github并创建一个仓库&#xff0c;这个很简单&#xff0c;网上教程也很多&#xff0c;就不展开说了 2. 安装git&#xff0c;这个也很简单&#xff0c;不过这里有个问题就是你当前windows的用户名即&#xff1a;C/Users/xxx 这个路径不要有中文&#xff0c;因为git …

Windows 下部署 SUNA 项目:虚拟环境尝试与最终方案

#工作记录 #回顾总结 本文记录了在 Windows 系统上&#xff0c;通过 PyCharm 图形界面&#xff08;尽量减少命令行操作&#xff09;部署 SUNA 项目时&#xff0c;针对不同虚拟环境方案的尝试过程、遇到的问题以及最终选择的可行方案&#xff0c;并补充了整体部署思路与推荐。…

无向图的点、边双连通分量

文章目录 点双连通分量边双连通分量 有向图的强连通分量&#xff1a;寒假学习笔记【匠心制作&#xff0c;图文并茂】——1.20拓扑、强连通分量、缩点 点双连通分量 在这之前&#xff0c;先让我们了解几个概念。 割点&#xff1a;删除一个点和其连出的边后&#xff0c;原图会…

第六十二节:深度学习-加载 TensorFlow/PyTorch/Caffe 模型

在计算机视觉领域,OpenCV的DNN(深度神经网络)模块正逐渐成为轻量级模型部署的利器。本文将深入探讨如何利用OpenCV加载和运行三大主流框架(TensorFlow、PyTorch、Caffe)训练的模型,并提供完整的代码实现和优化技巧。 一、OpenCV DNN模块的核心优势 OpenCV的DNN模块自3.3…

Spring @Autowired自动装配的实现机制

Spring Autowired自动装配的实现机制 Autowired 注解实现原理详解一、Autowired 注解定义二、Qualifier 注解辅助指定 Bean 名称三、BeanFactory&#xff1a;按类型获取 Bean四、注入逻辑实现五、小结 源码见&#xff1a;mini-spring Autowired 注解实现原理详解 Autowired 的…

胜牌™全球成为2026年FIFA世界杯™官方赞助商

胜牌全球将首次与国际足联&#xff08;FIFA&#xff09;旗舰赛事建立合作关系。 此次赞助恰逢美国首个润滑油品牌即将迎来160周年之际&#xff0c;其国际扩张步伐正在加快。 在这项全球顶级赛事筹备期间&#xff0c;胜牌全球将通过各种富有创意的零售和体验活动与球迷互动。 …

YOLOV7改进之融合深浅下采样模块(DSD Module)和轻量特征融合模块(LFI Module)

目录 一、研究背景​ 二. 核心创新点​ ​2.1 避免高MAC操作​ ​2.2 DSDM-LFIM主干网络​ 2.3 P2小目标检测分支​ ​3. 代码复现指南​ 环境配置 关键修改点 ​4. 实验结果对比​ 4.1 VisDrone数据集性能 4.2 边缘设备部署 4.3 检测效果可视化 ​5. 应用场景​ …

【C/C++】chrono简单使用场景

chrono使用场景举例 1 输出格式化字符串 示例代码 auto now std::chrono::system_clock::now(); auto t std::chrono::system_clock::to_time_t(now); auto ms std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;std::ostrin…

Med-R1论文阅读理解-1

论文总结&#xff1a;Med-R1: Reinforcement Learning for Generalizable Medical Reasoning in Vision-Language Models 论文写了什么&#xff1f; 本文提出了一种名为 Med-R1 的新框架&#xff0c;旨在通过强化学习&#xff08;Reinforcement Learning, RL&#xff09;提升…

京东热点缓存探测系统JDhotkey架构剖析

热点探测使用场景 MySQL 中被频繁访问的数据 &#xff0c;如热门商品的主键 IdRedis 缓存中被密集访问的 Key&#xff0c;如热门商品的详情需要 get goods$Id恶意攻击或机器人爬虫的请求信息&#xff0c;如特定标识的 userId、机器 IP频繁被访问的接口地址&#xff0c;如获取用…

MCU_IO驱动LED

注意事项&#xff1a; 1、亮度要求较高的情况下&#xff0c;不能由IO直接驱动LED MCU_IO引脚输出的电压和电流较弱&#xff0c;如果对光的亮度有要求的话&#xff0c;需要使用三极管来驱动。 MCU_IO的电压一般为3.3V或者5V&#xff0c;输出电流一般10mA-25mA。 2、不同颜色…

MyBatis 深度解析:高效 Java 持久层框架实践指南(基于 3.5.10)

一、MyBatis 核心架构与设计哲学 MyBatis 作为半自动 ORM 框架&#xff0c;核心设计目标是在灵活性与开发效率之间取得平衡。与 Hibernate 等全自动 ORM 框架不同&#xff0c;MyBatis 允许开发者完全控制 SQL 编写&#xff0c;同时通过映射机制减少重复代码&#xff0c;特别适…

二叉树(二)

98.验证二叉树 中序遍历二叉树&#xff0c;每次遍历存下当前节点的值&#xff0c;遍历到下一个节点比较&#xff0c;根据二叉搜索树的特性&#xff0c;左<中<右有&#xff1a; 如果当前值小于或等于上一个的值&#xff0c;说明不是二叉搜索树 如果当前值大于上一个节点…

解决Vue3+uni-app导航栏高亮自动同步方案

路由跳转自动识别导航高亮实现方法 以下代码使用wd-tabbar组件实现路由跳转时自动同步导航栏高亮状态&#xff0c;适用于所有的Vue3uni-app项目。 请根据自身使用框架类型完成&#xff0c;也可根据我使用的UI组件进行完成地址如下&#xff1a; Tabbar 标签栏 | Wot UI &#…

免费论文查重与AI检测工具推荐

文章目录 概要一、PaperPass二、PaperYY注意 概要 毕业季&#xff0c;总少不了查重这一步&#xff0c;甚至查 AI 率。推荐两款免费查重AIGC检测的工具。 论文免费查重查AI&#xff1a; https://paperpass.com/ https://www.paperyy.com/ 一、PaperPass 网址&#xff1a; ht…

4、ubuntu系统 | 文本和目录操作函数

1、目录操作函数 ls(列出目录内容) 用途:列出指定目录中的文件和子目录。语法:ls [选项] [路径]常用选项: -l:以长格式显示文件详细信息(权限、所有者、大小、时间等)。-a:显示隐藏文件(以.开头的文件)。-R:递归列出子目录内容。# 列出当前目录下的所有文件和子目…

C++--范围for循环详解

范围 for 循环是 C11 引入的语法特性&#xff0c;用于简化遍历容器或数组元素的过程。它比传统 for 循环更简洁安全&#xff0c;特别适合初学者。以下是详细讲解&#xff1a; 基本语法 for (元素类型 变量名 : 容器/数组) {// 循环体&#xff08;使用变量名访问当前元素&#…

RDMA简介1之RDMA开发必要性

为了满足大批量数据的采集、存储与传输需求&#xff0c;越来越多的数据密集型应用如机器学习、雷达、金融风控、航空航天等选择使用现场可编程逻辑门阵列作为数据采集前端硬件来实现高性能的数据采集系统。FPGA凭借其高灵活性、高并行能力及可高度定制化的特点&#xff0c;能够…