--- title: 设计模式之职责链模式 date: 2015-01-22 14:34:00 order: 20 categories: - 设计 - 设计模式 tags: - 设计 - 设计模式 permalink: /pages/8fa92451/ --- # 设计模式之职责链模式 ## 意图 **职责链模式**(Chain Of Responsibility) 是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。 将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 ## 适用场景 - 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。 - 当必须按顺序执行多个处理者时, 可以使用该模式。 - 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。 ## 结构 ### 结构说明 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172147.png) 1. **处理者** (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。 2. **基础处理者** (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。 通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。 3. **具体处理者** (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。 处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。 4. **客户端** (Client) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。 ### 结构代码范式 **Handler** : 定义一个处理请求的接口。(**_可选的_**)实现设置后继者的方法。 ```java abstract class Handler { protected Handler successor; public void SetSuccesssor(Handler successor) { this.successor = successor; } public abstract void HandlerRequest(int request); } ``` **ConcreteHandler** : 处理它所负责的请求,可以访问它的后继者,如果可处理该请求,就处理之,否则就将请求转发给它的后继者。 ```java class ConcreteHandler1 extends Handler { @Override public void HandlerRequest(int request) { if (request >= 0 && request < 10) { System.out.println("ConcreteHandler1 处理请求 " + request); } else if (null != successor) { successor.HandlerRequest(request); } } } class ConcreteHandler2 extends Handler { @Override public void HandlerRequest(int request) { if (request >= 10 && request < 20) { System.out.println("ConcreteHandler2 处理请求 " + request); } else if (null != successor) { successor.HandlerRequest(request); } } } class ConcreteHandler3 extends Handler { @Override public void HandlerRequest(int request) { if (request >= 20 && request < 30) { System.out.println("ConcreteHandler3 处理请求 " + request); } else if (null != successor) { successor.HandlerRequest(request); } } } ``` **Client** : 需要设置一个职责链的各环节对象串联起来。 ```java public class ChainOfResponsibilityPattern { public static void main(String[] args) { Handler h1 = new ConcreteHandler1(); Handler h2 = new ConcreteHandler2(); Handler h3 = new ConcreteHandler3(); h1.SetSuccesssor(h2); h2.SetSuccesssor(h3); int[] requests = {2, 29, 9, 15, 4, 19}; for (int i : requests) { h1.HandlerRequest(i); } } } ``` ## 伪代码 在本例中, **责任链**模式负责为活动的 GUI 元素显示上下文帮助信息。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172324.png) 应用程序的 GUI  通常为对象树结构。 例如, 负责渲染程序主窗口的 `对话框`类就是对象树的根节点。 对话框包含 `面板` , 而面板可能包含其他面板, 或是 `按钮`和 `文本框`等下层元素。 只要给一个简单的组件指定帮助文本, 它就可显示简短的上下文提示。 但更复杂的组件可自定义上下文帮助文本的显示方式, 例如显示手册摘录内容或在浏览器中打开一个网页。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172415.png) 当用户将鼠标指针移动到某个元素并按下 `F1`键时, 程序检测到指针下的组件并对其发送帮助请求。 该请求不断向上传递到该元素所有的容器, 直至某个元素能够显示帮助信息。 ```java // 处理者接口声明了一个创建处理者链的方法。还声明了一个执行请求的方法。 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() ``` ## 与其他模式的关系 - [责任链模式](https://refactoringguru.cn/design-patterns/chain-of-responsibility)、 [命令模式](https://refactoringguru.cn/design-patterns/command)、 [中介者模式](https://refactoringguru.cn/design-patterns/mediator)和[观察者模式](https://refactoringguru.cn/design-patterns/observer)用于处理请求发送者和接收者之间的不同连接方式: - *责任链*按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。 - *命令*在发送者和请求者之间建立单向连接。 - *中介者*清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。 - *观察者*允许接收者动态地订阅或取消接收请求。 - [责任链](https://refactoringguru.cn/design-patterns/chain-of-responsibility)通常和[组合模式](https://refactoringguru.cn/design-patterns/composite)结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。 - [责任链](https://refactoringguru.cn/design-patterns/chain-of-responsibility)的管理者可使用[命令模式](https://refactoringguru.cn/design-patterns/command)实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。 还有另外一种实现方式, 那就是请求自身就是一个*命令*对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。 - [责任链](https://refactoringguru.cn/design-patterns/chain-of-responsibility)和[装饰模式](https://refactoringguru.cn/design-patterns/decorator)的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。 [责任链](https://refactoringguru.cn/design-patterns/chain-of-responsibility)的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种*装饰*可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。 ## 案例 **使用示例:** 责任链模式在 Java 程序中并不常见, 因为它仅在代码与对象链打交道时才能发挥作用。 该模式最流行的使用案例之一是在 GUI 类中将事件向上传递给父组件。 另一个值得注意的使用案例是依次访问过滤器。 下面是该模式在核心 Java 程序库中的一些示例: - [`javax.servlet.Filter#doFilter()`](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) - [`java.util.logging.Logger#log()`](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log-java.util.logging.Level-java.lang.String-) **识别方法:** 该模式可通过一组对象的行为方法间接调用其他对象的相同方法来识别, 而且所有对象都会遵循相同的接口。 ## 参考资料 - [《Head First 设计模式》](https://book.douban.com/subject/2243615/) - [《大话设计模式》](https://book.douban.com/subject/2334288/) - [设计模式教程](https://refactoringguru.cn/design-patterns/catalog)