设计模式-行为模式之Chain-Of-Responsibility

来源:互联网 发布:万里独行知马力 编辑:程序博客网 时间:2024/05/01 01:51

更多请移步我的博客

意图

责任链(Chain Of Responsibility)是一种行为模式,通过给多个对象一个机会去处理请求的的方式来避免请求发送者和接受者的耦合。责任链接收对象并且沿着链条传递它,直到一个对象来处理它。

问题

假设你在做一个订单系统。你第一个任务就是限制用户对系统的访问,只有已经授权的用户可以创建订单。另外,一些用户拥有管理员权限,可以访问全部的订单。

你意识到这些检查必须顺序处理。程序能够在任何时候对用户进行尝试认证,只要用户的证书被请求传递。但是,如果未能对用户进行身份验证,则无法检查用户的权限。

几个月后,你已经实现了这些顺序检查。

  • 你的同时建议,将原始数据直接交给代码处理不安全。所以你添加了一个额外校验步骤来验证请求数据。

  • 之后,其他人建议协同无法应对暴力的密码破解。为了解决这个问题,你添加了另一个检查来过滤重复使用相同用户名但是失败的请求。

  • 你甚至添加了缓存来提升订单在高负载下的性能。

不幸的是,随着新特性的增加,代码变得越来越臃肿。甚至,你为了保护其他页面,把这些检查代码的一部分做了拷贝,导致出现重复代码片段。

这个系统变得难以维护。但是,一天你收到了重构系统的任务…

解决

和其他行为模式很像,责任链依赖于将行为转化为独立的对象。在我们的例子中,每个检查都将被移动到不同的类中,这些类只有一个方法来执行这些检查。这个方法通过参数来接收请求数据。

现在,到了有趣的部分。模式建议连接这些对象到一个链条中。 每个处理者都有一个字段来存储这个链条中下一个处理者的引用。不管什么时候,一个处理者接收到一个请求,它可以把请求传递给链条中在其之后的处理者。这个请求沿着链条旅行知道所有的处理者都有机会来处理它。

最后,一个处理者不需要在继续传递这个请求。有两种流行的方式来处理这个想法。

在我们访问过滤的例子中,处理者排队并挨个处理他们的检查。流的终点只可能是某个检查失败或者到达链条尾部。

但还有一个略微不同的方式,处理者只会传递那些它们自己无法处理的请求。否则,它们执行自己的业务并且终止链条的执行。这个选项在处理GUI组件事件时很常见。

比如,当用户点击一个按钮,这个事件便沿着由按钮开始的组件链传播,传到他们的父组件,像form和panel,直到应用的窗口停止。这个事件被链条中第一个可以处理它的组件处理。这个例子值得一提,因为它告诉我们一个链条可以从一个树结构中抽离出来。

所有的处理者类都需要遵循一样的接口。这将让你可以在运行时使用各个处理者组合一个链条,而代码不会和处理者的具体实现类耦合。每个具体的处理者应该只关心自己的execute方法。

现实世界的类比

技术支持

你为你的PC买了一个新的显卡。Windows可以自动检测并启用他。但你钟爱的Linux却无法使用新硬件。抱着微小的希望,你打电话给技术支持。

首先,你听到了自动应答的机器人声音。它提出了九种解决各种问题的流行解决方案,但没有一个与你的问题有关。过了一会,机器把你转给在线客服。

客服也没有给出有用的解决方法,于是你请求联系一个正确的工程师。

客服把你转给了工程师。最后,这个工程师告诉你到哪下载显卡驱动及如何在Linux中怎么安装。于是,你愉快的结束了这通电话。

结构

structure.png

  1. Handler为所有具体的处理者声明一个通用接口。通常,它只有一个处理请求的方法,但有时候也会有设置链条下一个处理者的方法。

  2. Base Handler是可选的类,它可以包含负责构建维护对象责任链的模版代码。

    这个类可以包含一个字段来存储链条中下一个处理者。使用这个字段,客户端可以将多个处理者链接到一个链中。这个字段可以通过构造方法或者一个set方法来控制。这个类也可能有一个基本处理方法的实现,这个方法检查下一个处理者是否存在然后把执行传递给它。

  3. Concrete Handlers包含处理请求的实际代码。接收到请求后,处理者必须决定是否处理该请求,另外还要决定是否在链条中继续传递它。

    处理者通常是独立的和不可变的,通过构造函数参数一次性接收所有必要的数据。

  4. Client可以只组装一次链条或者依赖于程序逻辑动态组装。注意,一个请求可以被发送到链中的任何处理者,它并不总必须是第一个。

伪代码

在这个例子中,责任链负责显示活动UI元素关联的一个上下文帮助。

这些GUI元素是树状结构。Dialog类渲染树根的主窗口。中间层由Panel组成。叶子结点有:Component、Button、TextEdit等。

一个Component能够显示上下文提示,只要它有帮助文本。一些复杂的组件有他们自己的方式来显示上下文帮助。

当用户将鼠标光标指向组件并按下F1时,应用抓取这个组件并发送帮助请求。这个请求向上传递给所有父容器知道这个组件可以显示帮助。

// Handler interface.interface ComponentWithContextualHelp is    method showHelp() is// Base class for simple components.abstract class Component implements ContextualHelp is    field tooltipText: string    // Container, which contains component, severs as a following object    // in chain.    protected field container: Container    // Component shows tooltip if there is a help text assigned to it. Otherwise    // it forwards the call to the container if it exists.    method showHelp() is        if (tooltipText != null)            Show tooltip.        else            container.showHelp()// Containers can contain both simple components and other container as// children. The chain relations are established here. The class inherits// showHelp behavior from its parent.abstract class Container extends Component is    protected field children: array of Component    method add(child) is        children.add(child)        child.container = this// Primitive components may be fine with default help implementation...class Button extends Component is    // ...// But complex components may override the default implementation. If a help can// not be provided in a new way, the component can always call the base// implementation (see Component class).class Panel extends Container is    field modalHelpText: string    method showHelp() is        if (modalHelpText != null)            Show modal window with a help text.        else            parent::showHelp()// ...same as above...class Dialog extends Container is    field wikiPage: string    method showHelp() is        if (wikiPage != null)            Open a wiki help page.        else            parent::showHelp()// Client code.class Application is    // Each application configures the chain differently.    method createUI() is        dialog = new Dialog("Budget Reports")        dialog.wikiPage = "http://..."        panel = new Panel(0, 0, 400, 800)        panel.modalHelpText = "This panel does..."        ok = new Button(250, 760, 50, 20, "OK")        ok.tooltipText = "This is a OK button that..."        cancel = new Button(320, 760, 50, 20, "Cancel")        // ...        panel.add(ok)        panel.add(cancel)        dialog.add(panel)    // Imagine what happens here.    method onF1KeyPress() is        component = this.getComponentAtMouseCoords()        component.showHelp()

适用性

  • 当一个程序有几个处理不同请求的处理者,但事先并不知道过来的是什么类型的请求。

    你要把几个处理者放到一个链条中。请求沿着链条传递直到有个处理者能够处理它。

  • 当要以特定顺序执行处理程序。

    责任链允许按照给定的顺序依次执行处理者。

  • 当有很多对象来处理请求,并且它们的顺序动态改变。

    责任链允许对一个存在的链条中的处理者进行新增、移除或者排序操作。

如何实现

  1. 声明Handler接口,包含一个处理请求的方法。决定如何传递请求的信息到方法中。最灵活的方法是将请求数据转换为对象并传递给处理方法。

  2. 为了减少重复的样板代码,从Handler接口派生出一个抽象BaseHandler类是很值得的。

    添加一个字段来保存下一个处理者的引用。这个字段可以从构造参数中获得初始化数据。你也可以定义一个set方法来修改这个字段。但仅当你想要在运行时想要修改链条才需要这么做。

    实现处理方法,以便将请求转发给链中的下一个对象(如果有的话)。具体的处理者能够通过调用父类方法来转发请求。因此,它们不需要访问引用字段,你就可以把它声明为private了。

  3. 创建ConcreteHandler子类并且实现它们的处理方法。每个处理者在收到请求时应做出两个决定:

    • 是否要处理这个请求。
    • 是否要继续传递这个请求。
  4. Client可以自己组装链条也可以从其他对象接收已经构造好的链条。在后一种情况下,可以采用工厂对象通过应用配置护着环境变量来构建链条。

  5. Client可能触发链条中任意一个处理者,不仅仅只是第一个。这个调用将会沿着链条传递直到链条结尾或者一些处理者拒绝进一步传递它。

  6. 由于链条的动态特性,Client应该准备好处理以下情况:

    • 有时一个链可能包含一个单一的链接。
    • 一些请求可能无法到达链条的尾部。
    • 一些请求到达链条尾部还未被处理。

优点

  • 减少请求发送者和接受者的耦合。

  • 遵循单一职责原则。

  • 遵循开闭原则。

缺点

  • 一些请求肯能到链条尾部仍未被处理。

和其他模式的关系

  • Chain Of Responsibility,Command,Mediator和Observer处理连接请求的发送者和接收者的各种方式:

    • 责任链沿着潜在接收者的动态链顺序传递一个请求,直到其中一个处理这个请求。

    • 命令模式建立从发送者到接收者的单向连接。

    • 调解模式持有发送者和接收者间接引用。

    • 观察者会在同一时间把一个请求发送给所有关心的接受者,但是允许它们动态的确定是否继续订阅和取消订阅后面的请求。

  • 责任链通常和组合(Composite)结合使用。在这种情况下,一个组件的父类可以看作是他的后继者。

  • 责任链中的处理者可以表示为命令(Command)。在这种情况下,许多不同的操作可以在由请求表示的相同上下文中执行。

    但还有另外一种方式,请求本身就是一个Command对象,沿着对象链传递。这种情况下,相同的操作可以在由链条对象表示的不同上下文中执行。

  • 责任链和装饰者(Decorator)的类结构很相似。它们都依赖于递归组合来在一系列对象中传递执行。但是它们也有几个关键区别。

    责任链的处理者能够随意执行动作,之间相互独立。它们也能够随意终止请求的进一步传递。另一方面,各个装饰者扩展一个特定行为并应该保持其接口一致。另外,装饰者不允许随意中断链条的执行。

小结

责任链允许请求烟盒潜在的处理链传递直到某个处理者处理这个请求。这种模式允许多个对象处理这个请求而不发送者类不需要和具体接受者类耦合。这个链可以在运行时动态组合遵循标准处理者接口的任何处理者。

在Java中比较流行的使用样例:在GUI类中向父组件传递事件;过滤访问请求。

在Java的核心类库中有些例子:

  • javax.servlet.Filter#doFilter()
  • java.util.logging.Logger#log()

当我们发现组织结构类似以下描述时,可能就采用了责任链模式:一个对象的行为方法间接调用其他对象中的相同方法,而所有对象都遵循共同的接口。

参考

翻译整理自:https://refactoring.guru/design-patterns/chain-of-responsibility

阅读全文
0 0
原创粉丝点击