- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
本文以 java-design-patterns 仓库中的 chain-of-responsibility 模块为核心,系统讲解责任链(Chain of Responsibility)这一行为型设计模式:它如何将请求的发送者与接收者解耦,让多个处理对象依次获得处理机会,直到链上某个环节接手。读完本文,你将掌握责任链模式的意图、适用场景、优劣势,并能结合仓库源码理解其接口设计、优先级排序机制与测试验证方式,直接将其复用到日志过滤、中间件、审批流等真实业务中。
模式概述:责任链解决什么问题
责任链模式(Chain of Responsibility)是 Gang of Four 提出的经典行为型设计模式,别名包括Chain of Command(命令链)、Chain of Objects(对象链)、Responsibility Chain(职责链)。
它的核心意图是:解耦请求的发送者与接收者——不把请求直接绑定到某个具体处理者,而是让多个对象都有机会处理请求。这些接收对象被组织成一条链,请求沿着链向后传递,直到某个对象将其处理完毕。正如本模块入口类 App.java 的注释所概括的:责任链由一组命令对象和一系列处理对象组成,每个处理对象内部包含判断自己能否处理该类命令的逻辑,无法处理的命令则交给链中的下一个处理对象,同时提供在链尾追加新处理对象的机制。
现实世界类比:技术支持呼叫中心
设想一个技术支持呼叫中心:每一级支持人员都是链上的一个处理节点。客户来电后,请求先到达一线支持代表;如果问题简单,一线直接解决;如果问题复杂,则升级给二线技术员;若仍未解决,再继续向更高级别升级,直到有能力的专家接手。每一级都是链上的一个 handler,请求沿链逐级传递,直到找到合适的处理者——发送者无需知道具体由谁处理,这正是责任链模式在现实中的典型映射。
一句话通俗解释
责任链帮助我们构建一条对象链:请求从链的一端进入,在对象之间依次传递,直到遇到一个合适的处理者将其接住。
Wikipedia 定义
在面向对象设计中,责任链模式由命令对象的源头和一系列处理对象组成。每个处理对象包含定义它能处理哪些命令对象的逻辑,其余命令则传递给链中的下一个处理对象。
责任链的请求流转流程
下图完整呈现了请求在责任链中的流转逻辑:客户端发起请求,从 Handler 1 开始依次判断Can Handler X process it?,能处理则就地处理(Handler X processes request),不能处理则交给下一个处理者;若整条链都没有处理者能接住,则请求最终落入Request unhandled(未被处理)状态。
从这张流程图可以提炼出责任链的两个关键设计决策:
- 每个节点都要回答"我能否处理"——在仓库实现中对应
RequestHandler.canHandleRequest(Request)。 - 链的终点必须考虑"无人处理"的兜底——这也是下文"优缺点"中会讨论的 catch-all handler 问题。
编程示例:兽人王国的命令链
在 java-design-patterns 的 chain-of-responsibility 模块中,责任链以一个生动的"兽人王国"场景演示:兽人国王发出洪亮的命令,最先响应的是指挥官(commander),然后是军官(officer),最后是士兵(soldier),三者构成一条责任链。
请求对象:Request 与 RequestType
首先看请求载体 Request.java:
@Getter public class Request { private final RequestType requestType; private final String requestDescription; private boolean handled; public Request(final RequestType requestType, final String requestDescription) { this.requestType = Objects.requireNonNull(requestType); this.requestDescription = Objects.requireNonNull(requestDescription); } public void markHandled() { this.handled = true; } @Override public String toString() { return getRequestDescription(); } } public enum RequestType { DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX }从源码看,Request有几个值得注意的实现细节:
@Getter(Lombok)自动生成getRequestType()、getRequestDescription()、isHandled();- 构造器对
requestType与requestDescription都执行Objects.requireNonNull,从源头杜绝空请求进入责任链; handled字段是单向状态机:只能通过markHandled()从未处理变为已处理,不存在"取消处理"的反向操作——正如 Request.java 的注释所强调的;RequestType枚举定义了三类请求:DEFEND_CASTLE(守城)、TORTURE_PRISONER(审讯囚犯)、COLLECT_TAX(收税),每一类恰好对应链上的一个处理者。
处理者接口:RequestHandler
责任链的抽象处理者角色是接口 RequestHandler.java:
public interface RequestHandler { boolean canHandleRequest(Request req); int getPriority(); void handle(Request req); String name(); }接口的四个方法各司其职:
| 方法 | 作用 | 在本示例中的实现 |
|---|---|---|
canHandleRequest(Request) | 判断当前处理者能否处理该请求 | 按RequestType匹配 |
getPriority() | 返回处理优先级,用于确定链上顺序 | 士兵 1、指挥官 2、军官 3 |
handle(Request) | 实际处理请求,并调用markHandled() | 记录日志并标记已处理 |
name() | 返回处理者名称,用于日志输出 | 如 "Orc commander" |
三个具体处理者:OrcCommander / OrcOfficer / OrcSoldier
三个具体处理者均实现RequestHandler接口,代码结构完全对称。以 OrcCommander.java 为例:
@Slf4j public class OrcCommander implements RequestHandler { @Override public boolean canHandleRequest(Request req) { return req.getRequestType() == RequestType.DEFEND_CASTLE; } @Override public int getPriority() { return 2; } @Override public void handle(Request req) { req.markHandled(); LOGGER.info("{} handling request \"{}\"", name(), req); } @Override public String name() { return "Orc commander"; } }OrcOfficer与OrcSoldier的定义方式完全相同,差异仅在各自的处理类型与优先级上。综合三个类的源码,责任链的"职责分配"如下表:
| 处理者 | 源码文件 | 能处理的请求类型 | 优先级 | 名称 |
|---|---|---|---|---|
| OrcSoldier | OrcSoldier.java | COLLECT_TAX | 1 | Orc soldier |
| OrcCommander | OrcCommander.java | DEFEND_CASTLE | 2 | Orc commander |
| OrcOfficer | OrcOfficer.java | TORTURE_PRISONER | 3 | Orc officer |
注意:这里的优先级数字并非链的传递顺序,而用于"谁能优先接管请求"的排序。读者可以对比传统责任链(后一个 handler 持有前一个的引用、逐个next传递)与本实现的差异——本示例采用"每个 handler 独立判断 + 统一按优先级挑选"的方式,这是责任链模式的一种流式变体。
链的构建与请求分发:OrcKing
兽人国王 OrcKing.java 既是命令的发起者,也是责任链的构建者与分发器:
public class OrcKing { private List<RequestHandler> handlers; public OrcKing() { buildChain(); } private void buildChain() { handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier()); } public void makeRequest(Request req) { handlers .stream() .sorted(Comparator.comparing(RequestHandler::getPriority)) .filter(handler -> handler.canHandleRequest(req)) .findFirst() .ifPresent(handler -> handler.handle(req)); } }逐行拆解makeRequest的流水线逻辑:
buildChain()在构造器中完成链的装配,将三个处理者放入List<RequestHandler>;.stream().sorted(Comparator.comparing(RequestHandler::getPriority))按优先级升序排序(士兵 1 → 指挥官 2 → 军官 3);.filter(handler -> handler.canHandleRequest(req))依次筛出能处理该请求的处理者;.findFirst()取优先级最高且能处理的那一个;.ifPresent(handler -> handler.handle(req))执行处理——只有真正处理时才会调用req.markHandled()。
这种"排序 + 过滤 + 取首"的实现,使得新增处理者、调整处理顺序都非常廉价:只需在buildChain()中增删成员或修改getPriority()返回值即可,无需改动分发逻辑。
入口程序与运行输出
入口程序 App.java 让兽人国王连续发出三条命令:
public static void main(String[] args) { var king = new OrcKing(); king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle")); king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner")); king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax")); }对应的控制台输出:
Orc commander handling request "defend castle" Orc officer handling request "torture prisoner" Orc soldier handling request "collect tax"三条命令分别由链上三个不同级别的处理者接住,直观展示了"请求沿链流转、按能力匹配处理者"的效果。
类图与源码结构全景
下图是本模块的 UML 类图(PlantUML 源文件位于 chain-of-responsibility.urm.puml,渲染图如下):
从类图与源码可以归纳出本模块的完整类结构(均位于com.iluwatar.chain包):
| 类 / 接口 | 角色 | 关键成员 |
|---|---|---|
RequestHandler(接口) | 抽象处理者 | canHandleRequest/getPriority/handle/name |
OrcCommander | 具体处理者 | 处理DEFEND_CASTLE,优先级 2 |
OrcOfficer | 具体处理者 | 处理TORTURE_PRISONER,优先级 3 |
OrcSoldier | 具体处理者 | 处理COLLECT_TAX,优先级 1 |
OrcKing | 链构建者 / 分发器 | 持有List<RequestHandler> handlers,makeRequest负责分发 |
Request | 请求对象 | requestType、requestDescription、handled状态 |
RequestType(枚举) | 请求类型 | DEFEND_CASTLE、TORTURE_PRISONER、COLLECT_TAX |
App | 程序入口 | main方法发起三条命令 |
关系要点:OrcKing通过-handlers关联RequestHandler接口(面向接口编程);OrcCommander、OrcOfficer、OrcSoldier三个具体处理者都以虚线实现该接口;Request通过requestType属性关联RequestType枚举。
如何运行与验证本模块
本模块是独立的 Maven 子模块(见 chain-of-responsibility/pom.xml),继承父项目com.iluwatar:java-design-patterns,依赖 slf4j-api、logback-classic 与 junit-jupiter-engine,并在maven-assembly-plugin中指定主类为com.iluwatar.chain.App。
在仓库根目录下,可执行以下命令查看运行结果:
# 运行责任链示例主程序 ./mvnw -pl chain-of-responsibility compile exec:java -Dexec.mainClass=com.iluwatar.chain.App # 运行该模块的全部单元测试 ./mvnw -pl chain-of-responsibility test本模块自带两组单元测试,可用于验证模式行为:
- OrcKingTest.java:构造了覆盖全部三种
RequestType的请求列表,逐个交给OrcKing.makeRequest后断言request.isHandled()为真——即所有请求最终都能被链上某个处理者接住; - AppTest.java:断言
App.main执行过程中不抛出异常,保证示例程序可稳定运行。
何时使用责任链模式
从本模式的意图出发,当以下场景出现时应考虑使用责任链:
- 有多个对象可能处理同一个请求,且处理者事先未知——处理者应由运行时的请求内容自动判定;
- 希望把请求发送给多个对象中的某一个,但不想显式指定接收者——发送者与接收者完全解耦;
- 可处理请求的对象集合需要动态指定——链的成员与顺序可以按需调整,而不影响调用方。
真实世界中的责任链应用
责任链模式在各类框架与中间件中广泛存在,典型场景包括:
- GUI 框架中的事件冒泡(Event Bubbling):一个事件可能被 UI 组件层级中的多个层次处理;
- 中间件框架:请求依次穿过一条由多个处理对象组成的链(如 Web 中间件管线);
- 日志框架:消息可依次传给一系列 logger,每个 logger 以不同方式处理(如级别过滤、格式化、输出);
java.util.logging.Logger#log():日志记录逐级向上传递的机制即是责任链的体现;javax.servlet.Filter#doFilter():Servlet 过滤器链让每个 Filter 决定放行还是拦截请求;- Apache Commons Chain:专门为责任链模式提供的通用实现库。
责任链模式的收益与代价
收益(Benefits)
- 降低耦合:请求发送者无需知道最终处理请求的具体 handler;
- 提升职责分配的灵活性:通过增删链成员或调整顺序,即可改变请求的处理方式;
- 可设置默认兜底处理者:当链上没有具体处理者能接住请求时,可配置一个 catch-all handler 兜底,避免请求落空。
代价(Trade-Offs)
- 调试与理解成本上升:链越长越复杂,请求的流转路径就越难一眼看清;
- 可能产生"无人处理"的悬空请求:如果链中缺少兜底处理者,请求会在遍历整条链后依然未被处理(对照上文流程图中的
Request unhandled分支); - 存在性能隐患:请求可能需依次穿过多个处理者才能命中正确的那个,甚至始终找不到,带来额外的遍历开销。
针对"无人处理"问题,本仓库示例通过OrcKingTest断言了三条命令全部被处理;但在更复杂的链设计中,建议显式追加一个兜底 handler(如"unknown request"处理器),或在makeRequest的ifPresent之外补充未处理分支。
与其他设计模式的关系
- Command 命令模式:可将请求封装为对象,封装后的命令对象恰好适合沿责任链传递;
- Composite 组合模式:责任链常与组合模式搭配使用,树的节点天然构成层次化的处理链;
- Decorator 装饰器模式:装饰器可以像责任链一样串联,逐层包裹并增强行为——区别在于装饰器通常全部执行,而责任链通常只由一个节点处理。
参考资料与延伸阅读
本文内容以本仓库 chain-of-responsibility 模块的 README、src/main/java/com/iluwatar/chain 下的全部源码、src/test 下的测试用例以及 etc 下的类图与流程图为准。责任链模式作为经典 GoF 行为型模式,在以下经典著作中均有系统论述,可作为延伸学习材料:Design Patterns: Elements of Reusable Object-Oriented Software、Head First Design Patterns、Pattern-Oriented Software Architecture, Volume 1: A System of Patterns、Refactoring to Patterns与Pattern Languages of Program Design 3。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Android Developer Roadmap与Chain of Responsibility模式:请求处理链
Android Developer Roadmap与Chain of Responsibility模式:请求处理链 Android开发中,请求处理逻辑的复杂性随
移动开发教程CS-Notes 设计模式精讲:责任链(Chain of Responsibility)模式原理与 Java 实现
CS Notes 设计模式精讲:责任链(Chain of Responsibility)模式原理与 Java 实现 责任链(Chain of Responsib
知识库文档教程Go职责链模式应用:golang-design-pattern中的请求处理链构建
Go职责链模式应用:golang design pattern中的请求处理链构建 职责链模式解决的核心问题 在日常开发中,你是否经常遇到需要多层审批的业务场景?
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考