设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
责任链模式
> 责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。 ![责任链设计模式](/media/202203/2022-03-06_1049490.4099109989132914.png) 顾名思义,责任链模式(Chain of Responsibility Pattern、职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。 ## 问题 假如你正在开发一个在线订购系统。 你希望对系统访问进行限制, 只允许认证用户创建订单。 此外, 拥有管理权限的用户也拥有所有订单的完全访问权限。 简单规划后, 你会意识到这些检查必须依次进行。 只要接收到包含用户凭据的请求, 应用程序就可尝试对进入系统的用户进行认证。 但如果由于用户凭据不正确而导致认证失败, 那就没有必要进行后续检查了。 在接下来的几个月里, 你实现了后续的几个检查步骤。 - 一位同事认为直接将原始数据传递给订购系统存在安全隐患。 因此你新增了额外的验证步骤来清理请求中的数据。 - 过了一段时间, 有人注意到系统无法抵御暴力密码破解方式的攻击。 为了防范这种情况, 你立刻添加了一个检查步骤来过滤来自同一 IP 地址的重复错误请求。 - 又有人提议你可以对包含同样数据的重复请求返回缓存中的结果, 从而提高系统响应速度。 因此, 你新增了一个检查步骤, 确保只有没有满足条件的缓存结果时请求才能通过并被发送给系统。 检查代码本来就已经混乱不堪, 而每次新增功能都会使其更加臃肿。 修改某个检查步骤有时会影响其他的检查步骤。 最糟糕的是, 当你希望复用这些检查步骤来保护其他系统组件时, 你只能复制部分代码, 因为这些组件只需部分而非全部的检查步骤。 系统会变得让人非常费解, 而且其维护成本也会激增。 你在艰难地和这些代码共处一段时间后, 有一天终于决定对整个系统进行重构。 ## 解决方案 与许多其他行为设计模式一样, 责任链会将特定行为转换为被称作处理者的独立对象。 在上述示例中, 每个检查步骤都可被抽取为仅有单个方法的类, 并执行检查操作。 请求及其数据则会被作为参数传递给该方法。 模式建议你将这些处理者连成一条链。 链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。 除了处理请求外, 处理者还负责沿着链传递请求。 请求会在链上移动, 直至所有处理者都有机会对其进行处理。 最重要的是: 处理者可以决定不再沿着链传递请求, 这可高效地取消所有后续处理步骤。 在我们的订购系统示例中, 处理者会在进行请求处理工作后决定是否继续沿着链传递请求。 如果请求中包含正确的数据, 所有处理者都将执行自己的主要行为, 无论该行为是身份验证还是数据缓存。 ![处理者依次排列,组成一条链](/media/202203/2022-03-06_1054140.2463223773853992.png) 不过还有一种稍微不同的方式 (也是更经典一种), 那就是处理者接收到请求后自行决定是否能够对其进行处理。 如果自己能够处理, 处理者就不再继续传递请求。 因此在这种情况下, 每个请求要么最多有一个处理者对其进行处理, 要么没有任何处理者对其进行处理。 在处理图形用户界面元素栈中的事件时, 这种方式非常常见。 例如, 当用户点击按钮时, 按钮产生的事件将沿着 GUI 元素链进行传递, 最开始是按钮的容器 (如窗体或面板), 直至应用程序主窗口。 链上第一个能处理该事件的元素会对其进行处理。 此外, 该例还有另一个值得我们关注的地方: 它表明我们总能从对象树中抽取出链来。 ![对象树的枝干可以组成一条链](/media/202203/2022-03-06_1055460.8562031573937878.png) 所有处理者类均实现同一接口是关键所在。 每个具体处理者仅关心下一个包含 execute 执行方法的处理者。 这样一来, 你就可以在运行时使用不同的处理者来创建链, 而无需将相关代码与处理者的具体类进行耦合。 ## 目的 建立一个对象链来按指定顺序处理调用。如果其中一个对象无法处理命令,它会委托这个调用给它的下一个对象来进行处理,以此类推。 ## 介绍 **意图:** 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 **主要解决:** 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。 **何时使用:** 在处理消息的时候以过滤很多道。 **如何解决:** **拦截的类都实现统一接口。** **关键代码:** Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。 **应用实例:** 1. 红楼梦中的"击鼓传花"。 2. JS 中的事件冒泡。 3. JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。 4. 日志框架,每个链元素自主决定如何处理日志消息。 5. 垃圾邮件过滤器。 6. 缓存:例如第一个对象是一个 Memcached 接口实例,如果 “丢失” 它会委托数据库接口处理这个调用。 7. Yii 框架: CFilterChain 是一个控制器行为过滤器链。执行点会有链上的过滤器逐个传递,并且只有当所有的过滤器验证通过,这个行为最后才会被调用。 **优点:** 1. 降低耦合度。它将请求的发送者和接收者解耦。 2. 简化了对象。使得对象不需要知道链的结构。 3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4. 增加新的请求处理类很方便。 **缺点:** 1. 不能保证请求一定被接收。 2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3. 可能不容易观察运行时的特征,有碍于除错。 **使用场景:** 1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3. 可动态指定一组对象处理请求。 **注意事项:** 在 JAVA WEB 中遇到很多应用。 ## 结构 ![责任链设计模式的结构](/media/202203/2022-03-06_1101170.8168707641933988.png) - 处理者 (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。 - 基础处理者 (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。 通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。 - 具体处理者 (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。 处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。 - 客户端 (Client) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。 ### 实例 我们创建抽象类 *AbstractLogger* ,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 *AbstractLogger* 。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。 ![责任链模式的 UML 图](https://www.runoob.com/wp-content/uploads/2014/08/2021-chain-of-responsibility.svg) ## 伪代码 在本例中, 责任链模式负责为活动的 GUI 元素显示上下文帮助信息。 ![责任链结构的示例](/media/202203/2022-03-06_1110100.9415571489091946.png) GUI 类使用组合模式生成。 每个元素都链接到自己的容器元素。 你可随时构建从当前元素开始的、 遍历其所有容器的元素链。 应用程序的 GUI 通常为对象树结构。 例如, 负责渲染程序主窗口的 对话框类就是对象树的根节点。 对话框包含 面板 , 而面板可能包含其他面板, 或是 按钮和 文本框等下层元素。 只要给一个简单的组件指定帮助文本, 它就可显示简短的上下文提示。 但更复杂的组件可自定义上下文帮助文本的显示方式, 例如显示手册摘录内容或在浏览器中打开一个网页。 当用户将鼠标指针移动到某个元素并按下 F1 键时, 程序检测到指针下的组件并对其发送帮助请求。 该请求不断向上传递到该元素所有的容器, 直至某个元素能够显示帮助信息。 ``` // 处理者接口声明了一个创建处理者链的方法。还声明了一个执行请求的方法。 interface ComponentWithContextualHelp is method showHelp() // 简单组件的基础类。 abstract class Component implements ComponentWithContextualHelp is field tooltipText: string // 组件容器在处理者链中作为“下一个”链接。 protected field container: Container // 如果组件设定了帮助文字,那它将会显示提示信息。如果组件没有帮助文字 // 且其容器存在,那它会将调用传递给容器。 method showHelp() is if (tooltipText != null) // 显示提示信息。 else container.showHelp() // 容器可以将简单组件和其他容器作为其子项目。链关系将在这里建立。该类将从 // 其父类处继承 showHelp(显示帮助)的行为。 abstract class Container extends Component is protected field children: array of Component method add(child) is children.add(child) child.container = this // 原始组件应该能够使用帮助操作的默认实现... class Button extends Component is // ... // 但复杂组件可能会对默认实现进行重写。如果无法以新的方式来提供帮助文字, // 那组件总是还能调用基础实现的(参见 Component 类)。 class Panel extends Container is field modalHelpText: string method showHelp() is if (modalHelpText != null) // 显示包含帮助文字的模态窗口。 else super.showHelp() // ...同上... class Dialog extends Container is field wikiPageURL: string method showHelp() is if (wikiPageURL != null) // 打开百科帮助页面。 else super.showHelp() // 客户端代码。 class Application is // 每个程序都能以不同方式对链进行配置。 method createUI() is dialog = new Dialog("预算报告") dialog.wikiPageURL = "http://..." panel = new Panel(0, 0, 400, 800) panel.modalHelpText = "本面板用于..." ok = new Button(250, 760, 50, 20, "确认") ok.tooltipText = "这是一个确认按钮..." cancel = new Button(320, 760, 50, 20, "取消") // ... panel.add(ok) panel.add(cancel) dialog.add(panel) // 想象这里会发生什么。 method onF1KeyPress() is component = this.getComponentAtMouseCoords() component.showHelp() ``` ## 应用场景 - 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。 该模式能将多个处理者连接成一条链。 接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。 这样所有处理者都有机会来处理请求。 - 当必须按顺序执行多个处理者时, 可以使用该模式。 无论你以何种顺序将处理者连接成一条链, 所有请求都会严格按照顺序通过链上的处理者。 - 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。 如果在处理者类中有对引用成员变量的设定方法, 你将能动态地插入和移除处理者, 或者改变其顺序。 ## 实现方式 1. 声明处理者接口并描述请求处理方法的签名。 确定客户端如何将请求数据传递给方法。最灵活的方式是将请求转换为对象,然后将其以参数的形式传递给处理函数。 2. 为了在具体处理者中消除重复的样本代码,你可以根据处理者接口创建抽象处理者基类。 该类需要有一个成员变量来存储指向链上下个处理者的引用。你可以将其设置为不可变类。但如果你打算在运行时对链进行改变,则需要定义一个设定方法来修改引用成员变量的值。 为了使用方便,你还可以实现处理方法的默认行为。如果还有剩余对象,该方法会将请求传递给下个对象。具体处理者还能够通过调用父对象的方法来使用这一行为。 3. 依次创建具体处理者子类并实现其处理方法。每个处理者在接收到请求后都必须做出两个决定: * 是否自行处理这个请求。 * 是否将该请求沿着链进行传递。 4. 客户端可以自行组装链,或者从其他对象处获得预先组装好的链。在后一种情况下,你必须实现工厂类以根据配置或环境设置来创建链。 5. 客户端可以触发链中的任意处理者,而不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递,或者请求到达链尾。 6. 由于链的动态性,客户端需要准备好处理以下情况: * 链中可能只有单个链接。 * 部分请求可能无法到达链尾。 * 其他请求可能直到链尾都未被处理。 ## 优点 - 你可以控制请求处理的顺序。 - 单一职责原则。 你可对发起操作和执行操作的类进行解耦。 - 开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。 ## 缺点 - 部分请求可能未被处理。 ## 与其他模式的关系 - 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式: - 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 - 命令在发送者和请求者之间建立单向连接。 - 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - 观察者允许接收者动态地订阅或取消接收请求。 - 责任链通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。 - **责任链的管理者可使用命令模式实现。** 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。 还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。 - 责任链和装饰模式的类结构非常相似。 两者都**依赖递归组合将需要执行的操作传递给一系列对象**。 但是, 两者有几点重要的不同之处。 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, **装饰无法中断请求的传递**。 ## 示例代码 ``` /** * The Handler interface declares a method for building the chain of handlers. * It also declares a method for executing a request. */ interface Handler { setNext(handler: Handler): Handler; handle(request: string): string; } /** * The default chaining behavior can be implemented inside a base handler class. */ abstract class AbstractHandler implements Handler { private nextHandler: Handler; public setNext(handler: Handler): Handler { this.nextHandler = handler; // Returning a handler from here will let us link handlers in a // convenient way like this: // monkey.setNext(squirrel).setNext(dog); return handler; } public handle(request: string): string { if (this.nextHandler) { return this.nextHandler.handle(request); } return null; } } /** * All Concrete Handlers either handle a request or pass it to the next handler * in the chain. */ class MonkeyHandler extends AbstractHandler { public handle(request: string): string { if (request === 'Banana') { return `Monkey: I'll eat the ${request}.`; } return super.handle(request); } } class SquirrelHandler extends AbstractHandler { public handle(request: string): string { if (request === 'Nut') { return `Squirrel: I'll eat the ${request}.`; } return super.handle(request); } } class DogHandler extends AbstractHandler { public handle(request: string): string { if (request === 'MeatBall') { return `Dog: I'll eat the ${request}.`; } return super.handle(request); } } /** * The client code is usually suited to work with a single handler. In most * cases, it is not even aware that the handler is part of a chain. */ function clientCode(handler: Handler) { const foods = ['Nut', 'Banana', 'Cup of coffee']; for (const food of foods) { console.log(`Client: Who wants a ${food}?`); const result = handler.handle(food); if (result) { console.log(` ${result}`); } else { console.log(` ${food} was left untouched.`); } } } /** * The other part of the client code constructs the actual chain. */ const monkey = new MonkeyHandler(); const squirrel = new SquirrelHandler(); const dog = new DogHandler(); monkey.setNext(squirrel).setNext(dog); /** * The client should be able to send a request to any handler, not just the * first one in the chain. */ console.log('Chain: Monkey > Squirrel > Dog\n'); clientCode(monkey); console.log(''); console.log('Subchain: Squirrel > Dog\n'); clientCode(squirrel); ``` 输出: ``` Chain: Monkey > Squirrel > Dog Client: Who wants a Nut? Squirrel: I'll eat the Nut. Client: Who wants a Banana? Monkey: I'll eat the Banana. Client: Who wants a Cup of coffee? Cup of coffee was left untouched. Subchain: Squirrel > Dog Client: Who wants a Nut? Squirrel: I'll eat the Nut. Client: Who wants a Banana? Banana was left untouched. Client: Who wants a Cup of coffee? Cup of coffee was left untouched. ```
追风者
2022年3月29日 19:57
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码