- 文档
- 技术博客
- 教程
【免费下载链接】weekly
前端精读周刊。帮你理解最前沿、实用的技术。
本篇精读文章来自仓库 设计模式/179.精读《设计模式 - Chain of Responsibility 职责链模式》.md,以 JS 事件冒泡、后端中间件机制、通用帮助文案三个日常场景切入,系统拆解职责链模式(Chain of Responsibility)的意图、结构与实现。读完你将掌握:为什么说event.stopPropagation()就是职责链的"阻断能力"、如何用十几行代码手写一个可穿透可中断的职责链,以及该模式与组合模式(Composite)组合使用、在搭建引擎中间件中的真实落地方式。
职责链模式是什么
Chain of Responsibility(职责链模式)属于行为型模式。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式——比如对操作的处理应该如何传递等等。
意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
几乎所有设计模式,在了解到它之前,你大概率已经在实战中遇到过了,因此设计模式的确是从实践中得出的真知。但另一方面,如果没有实战的理解,单看设计模式是枯燥的、难以理解的,因此学习设计模式时,要结合实际问题思考。
三个场景:什么时候会用到职责链
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面三个例子能让你体会什么场景下会用到这种设计模式。
中间件机制
设想我们要为一个后端框架实现中间件(知道 Koa 的同学可以理解为 Koa 的洋葱模型),在代码中可以插入任意多个中间件,每个中间件都可以对请求与响应进行处理。
由于每个中间件只响应自己感兴趣的请求,因此只有运行时才知道这个中间件是否会处理请求。那么中间件机制应该如何设计,才能保证其功能和灵活性呢?
本仓库的 可视化搭建/275.组件值校验.md 就是一个真实佐证:搭建引擎通过createMiddleware传递中间件来拓展自定义校验规则:
import { createMiddleware } from "designer"; const myMiddleware = createMiddleware({ validateRules: { // 自定义校验规则,判断是否为空字符串 isEmptyString: (value, options?: { errorMessage?: string }) => { if (value === "") { return true; } return options.errorMessage; }, }, });定义后即可在组件的valueValidator中使用这条自定义规则:
const input: ComponentMeta = { componentName: "input", element: Input, valueValidator: () => ({ isEmptyString: { errorMessage: "字符串必须为空", }, }), };可以看到,校验规则的集合本身就是一条"链":内置规则在前、自定义规则在后,每个规则各自决定是否"接住"校验请求,这正是职责链模式在真实业务框架中的落地形态。
通用帮助文案
如果一个大型系统中,任何一个模块点击都会弹出帮助文案,但并不是每个模块都有帮助文案的:如果一个模块没有帮助文案,则显示其父级的帮助文案,如果再没有,就继续冒泡到整个应用,展示应用级别的兜底帮助文案。这种系统应该如何设计?
这个需求本质上就是一条"文案查找链":模块自己 → 父级模块 → 应用兜底,每一级都是链上的一个 Handler,只有前一级"不处理"时请求才继续向后传递。
JS 事件冒泡机制
其实JS 事件冒泡机制就是个典型的职责链模式,因为任何 DOM 元素都可以监听比如onClick,不仅可以自己响应事件,还可以使用event.stopPropagation()阻止继续冒泡。
意图解释:站在点击事件的角度重新理解
JS 事件冒泡机制对前端来说太常见了,但我们换个角度,站在点击事件的角度理解,就能重新发现其设计的精妙之处:
点击事件是叠加在每层 DOM 上的,由于 DOM 对事件的处理和绑定是动态的,浏览器本身不知道哪些地方会处理点击事件,但又要让每层 DOM 拥有对点击事件的"平等处理权",所以就产生了冒泡机制,与事件阻止冒泡功能。
通用帮助文案和 JS 事件冒泡很类似,只是把点击事件换成了弹出帮助文案罢了,其场景机理是一样的。
回到职责链模式的意图本身:
意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
逐句拆解:
- "请求"指的是某个触发机制产生的请求,是一个通用概念。点击事件、帮助文案、HTTP 请求都可以视为"请求"。
- "避免请求的发送者和接收者之间的耦合关系":如果我们只有一个对象有处理请求的机会,那接收者就与发送者之间耦合了——其他接收者必须通过这个接收者才能继续处理,这种模式不够灵活。
- 后半句描述如何设计:将对象连成一条链,沿着链条传递该请求,直到有一个对象处理它为止。还要理解到,任何一个对象都拥有阻断请求继续传递的能力——对应到 JS 就是
stopPropagation()。
在中间件机制的例子中,后端 Web 框架对 Http 请求的处理就是个运用职责链模式的典型案例,因为后端框架要处理的请求是平行关系,任何请求都可能要求被响应,但对请求的处理是通过插件机制拓展的,且对每个请求的处理都是一个链条,存在处理、加工、再处理的逻辑关系。仓库 前沿技术/20.精读《Nestjs》文档.md 也指出:Nestjs 基于 Express 封装、以 Modules 组织应用,其中间件实现与 Modules 完美结合——中间件组成的处理链正是职责链模式的工程化体现。
结构图:一条可中断、可穿透的环路
原文档给出了职责链模式的标准结构(Handler → ConcreteHandler):
- Handler就是对请求的处理,可以看到这里是一条环路:只要处理完之后就可以交给下一个 Handler 进行处理,可以在中途拦截后中断,也可以穿透整条链路。
- ConcreteHandler是具体 Handler 的实现,它们都需要继承 Handler 以具备相同的
HandleRequest方法,这样每一个处理中间件就都拥有了处理能力,使得这些对象连成的链条可以对请求进行传递。
代码例子:两种职责链实现方式
职责链实现方式非常多,比如 Koa 的洋葱模型实现原理就值得再写一篇文章(可参考 co 源码 理解其背后的 Generator/Promise 调度)。这里仅介绍最简单场景的实现方案。
职责链的简单实现模式也分为两种:
- 每个对象本身维护到下一个对象的引用;
- 由 Handler 统一维护后继者。
下面例子使用 typescript 编写,采用"每个 Handler 持有后继引用"的方式:
public class Handler { private nextHandler: Handler public handle() { if(nextHandler) { nextHandler.handle() } } }每个 Handler 的默认行为就是触发下一个链条的handle,因此什么都不做的话,这个链条是完全打通的,因此我们可以在链条的任何一环进行处理。
处理的方式就是重写handle函数:
- 重写时维持对
nextHandler.handle()的调用 → 链条继续向后传递(穿透); - 重写时不调用 → 链条在此处终止(阻断)。
这与 JS 事件冒泡中的stopPropagation()、中间件机制中的"放行 / 不放行"是同一套思想:默认放行,显式阻断。
弊端:职责链的信任边界与适用边界
职责链模式并非银弹,有两个典型弊端:
其一,不保证每个中间件都有机会处理请求。因为中间件顺序的问题,后面中间件可能被前面的中间件阻断,因此当中间件之间存在不信任关系时,职责链模式并不能保证中间件调用的可靠性。也就是说,链上的"机会平等"只是结构上的平等,执行顺序上后置节点随时可能被前置节点"短路"。
其二,不要扩大设计模式的使用范围。对一堆对象的连续调用就没必要使用职责链模式,因为:
- 职责链适合对象数量不确定、是否处理请求由每个对象灵活决定的场景;
- 确定了对象数量以及是否调用的场景(顺序固定、逐一调用的管线),直接写循环或显式调用即可,引入职责链反而增加抽象成本。
总结:职责链与组合模式的组合
职责链模式是插件机制常用的设计模式,在事件机制、请求处理中有广泛的应用。本仓库的组件值校验(可视化搭建/275.组件值校验.md)正是通过中间件拓展校验规则的职责链实践;而中间件机制本身在 前沿技术/53.精读《插件化思维》.md 中也有独立论述,可以相互印证。
职责链模式还可以与组合模式组合使用:组合模式描述的是一种统一管理的树形结构(参见 设计模式/174.精读《设计模式 - Composite 组合模式》.md),每个节点都可以把自己的父节点作为后继节点。实际上 DOM 结构就是一种组合模式,事件冒泡就是在其基础上拓展的职责链模式——树给了节点"父级在哪里"的结构信息,职责链给了事件"沿父级向上传递"的流动规则,两者组合正是浏览器事件模型的设计内核。
原文档出处:设计模式/179.精读《设计模式 - Chain of Responsibility 职责链模式》.md,属于本仓库"设计模式"基础模块的一部分(可查看 readme.md 中"设计模式"目录)。本文作为其扩展解读,核心观点与代码示例均继承自原文档,并结合仓库内中间件校验、组合模式等源码级案例进行佐证。
- 文档
- 技术博客
- 教程
【免费下载链接】weekly
前端精读周刊。帮你理解最前沿、实用的技术。
相关推荐
CS-Notes 设计模式精讲:责任链(Chain of Responsibility)模式原理与 Java 实现
CS Notes 设计模式精讲:责任链(Chain of Responsibility)模式原理与 Java 实现 责任链(Chain of Responsib
知识库文档教程责任链模式(Chain of Responsibility)实战解析:Java 设计模式中的请求处理链
责任链模式(Chain of Responsibility)实战解析:Java 设计模式中的请求处理链 责任链(Chain of Responsibility)
示例工程教程前端模块化设计原则:打造高内聚低耦合的现代前端架构
前端模块化设计原则:打造高内聚低耦合的现代前端架构 前端模块化设计是构建可维护、可扩展Web应用的核心原则。随着前端项目规模不断扩大,模块化已从可选优化变为必备
文档技术博客教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考