圣诞大减价开始了!

责任链

也被称为:天哪, 指挥系统

意图

责任链是一种行为设计模式,允许您沿着处理程序链传递请求。在收到请求后,每个处理程序决定是处理该请求还是将其传递给链中的下一个处理程序。

责任链设计模式

问题

假设您正在开发一个在线订购系统。您希望限制对系统的访问,以便只有经过身份验证的用户才能创建订单。此外,具有管理权限的用户必须对所有订单具有完全访问权限。

经过一些计划后,您意识到这些检查必须按顺序执行。当应用程序接收到包含用户凭据的请求时,它可以尝试向系统验证用户。但是,如果这些凭证不正确,身份验证失败,就没有理由继续进行任何其他检查。

问题,通过责任链解决

请求必须通过一系列检查,然后订购系统才能处理它。

在接下来的几个月里,您实现了更多的连续检查。

  • 你的一个同事说直接把原始数据传给订购系统是不安全的。因此,您添加了一个额外的验证步骤来清除请求中的数据。

  • 后来,有人注意到该系统很容易被暴力破解密码。为了避免这种情况,您立即添加了一个检查,以过滤来自相同IP地址的重复失败请求。

  • 还有人建议,可以通过对包含相同数据的重复请求返回缓存结果来加快系统速度。因此,您添加了另一个检查,只有在没有合适的缓存响应时才允许请求通过系统。

随着每一次新的检查,代码变得更大、更混乱、更丑陋

代码越大,就越混乱。

检查的代码本来看起来就像一团乱,随着您添加每个新功能,代码变得越来越臃肿。更改一张支票有时会影响其他支票。最糟糕的是,当您试图重用检查来保护系统的其他组件时,您不得不重复一些代码,因为这些组件需要一些检查,而不是全部。

这个系统变得很难理解,维护起来也很昂贵。你在代码上苦苦挣扎了一段时间,直到有一天你决定重构整个代码。

解决方案

与许多其他行为设计模式一样,188bet平台电子游戏责任链依赖于将特定的行为转换为称为处理程序.在我们的例子中,每个检查都应该被提取到它自己的类中,使用一个方法来执行检查。请求及其数据作为参数传递给该方法。

该模式建议将这些处理程序链接到一个链中。每个链接的处理程序都有一个字段,用于存储对链中下一个处理程序的引用。除了处理请求外,处理程序还沿着链进一步传递请求。请求沿着链传递,直到所有处理程序都有机会处理它。

这里是最好的部分:处理程序可以决定不沿着链向下传递请求,并有效地停止任何进一步的处理。

在我们的排序系统示例中,一个处理程序执行处理,然后决定是否将请求沿链向下传递。假设请求包含正确的数据,所有处理程序都可以执行它们的主要行为,无论是身份验证检查还是缓存。

处理人员一个接一个排成一排,形成一个链条

处理人员一个接一个排成一排,形成一个链条。

然而,有一种稍微不同的方法(它更规范一点),即在接收到请求时,处理程序决定是否可以处理它。如果可以,它就不再传递请求。因此,要么只有一个处理程序处理请求,要么根本没有。在图形用户界面中处理元素堆栈中的事件时,这种方法非常常见。

例如,当用户单击按钮时,事件将通过GUI元素链传播,从按钮开始,沿着它的容器(如表单或面板),最后到达主应用程序窗口。事件由链中能够处理它的第一个元素处理。这个例子也值得注意,因为它显示了链总是可以从对象树中提取出来。

链可以由对象树的分支组成

链可以由对象树的分支组成。

所有处理程序类实现相同的接口是至关重要的。属性的具体处理程序应该只关心下面的处理程序执行方法。通过这种方式,您可以在运行时使用各种处理程序组合链,而无需将代码与它们的具体类耦合。

真实的模拟

与技术支持人员交谈可能会很困难

打给技术支持的电话可以通过多个接线员。

您刚刚在计算机上购买并安装了一个新的硬件。因为你是个极客,所以这台电脑安装了好几个操作系统。您尝试启动所有这些服务器,以查看是否支持硬件。Windows会自动检测并启用硬件。但是,您心爱的Linux拒绝与新硬件一起工作。带着一丝希望,你决定拨打写在盒子上的技术支持电话号码。

你听到的第一件事是自动回复机器人的声音。它为各种问题提供了9种流行的解决方案,但没有一个与您的情况相关。一段时间后,机器人将你连接到一个活生生的操作员。

可惜的是,接线员也无法给出任何具体的建议。他一直在引用手册上的冗长节选,不听你的意见。在听到“你试过关机再开机吗?”“这已经是第十次了,你要求联系一个合适的工程师。

最终,话务员将您的电话转给其中一名工程师,他可能已经在某个办公楼黑暗的地下室里孤独的服务器室里等待了几个小时的真人聊天。工程师会告诉您在哪里下载合适的新硬件驱动程序,以及如何在Linux上安装它们。最后,解决方案!你结束电话,满心欢喜。

结构

责任链设计模式的结构 责任链设计模式的结构
  1. 处理程序声明接口,用于所有具体处理程序。它通常只包含一个处理请求的方法,但有时它也可能有另一个方法来设置链上的下一个处理程序。

  2. 基础处理程序是一个可选类,您可以在其中放置所有处理程序类通用的样板代码。

    通常,该类定义一个字段,用于存储对下一个处理程序的引用。客户端可以通过将处理程序传递给前一个处理程序的构造函数或setter来构建链。类还可以实现默认的处理行为:它可以在检查其存在后将执行传递给下一个处理程序。

  3. 具体的处理程序包含处理请求的实际代码。在收到请求后,每个处理程序必须决定是否处理它,以及是否沿着链传递它。

    处理程序通常是自包含且不可变的,只通过构造函数接受一次所有必要的数据。

  4. 客户端可以只组合一次链,也可以动态组合链,这取决于应用程序的逻辑。请注意,请求可以发送到链中的任何处理程序—它不必是第一个处理程序。

伪代码

在本例中,责任链pattern负责显示活动GUI元素的上下文帮助信息。

责任链示例的结构

GUI类是使用Composite模式构建的。每个元素都链接到它的容器元素。在任何时候,您都可以构建一个元素链,从元素本身开始,遍历它的所有容器元素。

应用程序的GUI通常结构为对象树。例如,对话框类,它呈现应用程序的主窗口,将是对象树的根。对话框包含面板,其中可能包含其他面板或简单的低级元素,如按钮而且TextFields字段

一个简单的组件可以显示简短的上下文工具提示,只要该组件分配了一些帮助文本。但是更复杂的组件定义了自己显示上下文帮助的方式,比如显示手册的摘录或在浏览器中打开一个页面。

责任链示例的结构

这就是帮助请求遍历GUI对象的方式。

当用户将鼠标光标指向某个元素并按下F1键时,应用程序检测指针下的组件并向其发送帮助请求。请求在元素的所有容器中冒泡,直到到达能够显示帮助信息的元素。

处理程序接口声明了一个执行//请求的方法。//简单组件的基类。组件的容器充当//处理程序链中的下一个链接。protected field container: container //如果有帮助文本分配给组件,组件会显示一个工具提示。否则,它将调用转发到//容器(如果存在)。method showHelp() is if (tooltipText != null) //显示工具提示。else container.showHelp() //容器既可以包含简单组件,也可以包含其他的子容器。链关系在这里建立。类从//它的父类继承showHelp行为。组件方法add(child)的数组是children.add(child) child。container = this //基本元素组件可以使用默认帮助//实现… class Button extends Component is // ... // But complex components may override the default // implementation. If the help text can't 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 a modal window with the help text. else super.showHelp() // ...same as above... class Dialog extends Container is field wikiPageURL: string method showHelp() is if (wikiPageURL != null) // Open the wiki help page. else super.showHelp() // Client code. class Application is // Every application configures the chain differently. method createUI() is dialog = new Dialog("Budget Reports") dialog.wikiPageURL = "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 an 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()

适用性

当您的程序期望以各种方式处理不同类型的请求,但事先不知道请求的确切类型及其序列时,请使用责任链模式。

该模式允许您将多个处理程序链接到一个链中,并在收到请求时“询问”每个处理程序是否可以处理该请求。这样,所有处理程序都有机会处理请求。

当必须以特定顺序执行多个处理程序时,请使用该模式。

由于您可以以任意顺序链接链中的处理程序,因此所有请求都将完全按照您的计划通过链。

当处理程序集及其顺序在运行时应该改变时,请使用CoR模式。

如果在处理程序类中为引用字段提供setter,则可以动态地插入、删除或重新排序处理程序。

如何实施

  1. 声明处理程序接口并描述处理请求的方法的签名。

    决定客户机如何将请求数据传递给方法。最灵活的方法是将请求转换为对象,并将其作为参数传递给处理方法。

  2. 为了消除具体处理程序中重复的样板代码,可能值得创建从处理程序接口派生的抽象基处理程序类。

    该类应该有一个字段,用于存储链中下一个处理程序的引用。考虑将类设置为不可变的。但是,如果计划在运行时修改链,则需要定义一个setter来修改引用字段的值。

    您还可以为处理方法实现方便的默认行为,即将请求转发到下一个对象,除非已经没有对象了。具体处理程序将能够通过调用父方法来使用此行为。

  3. 逐个创建具体的处理程序子类并实现它们的处理方法。每个处理器在接收请求时应该做两个决定:

    • 它是否会处理请求。
    • 它是否会沿着链传递请求。
  4. 客户端可以自己组装链,也可以从其他对象接收预先构建的链。在后一种情况下,您必须实现一些工厂类来根据配置或环境设置构建链。

  5. 客户端可以触发链中的任何处理程序,而不仅仅是第一个。请求将沿着链传递,直到某个处理程序拒绝进一步传递它,或者直到它到达链的末端。

  6. 由于链的动态性质,客户端应该准备好处理以下场景:

    • 这个链条可以由一个单环组成。
    • 有些请求可能无法到达链的末端。
    • 其他的可能会到达链条的末端而没有得到处理。

利与弊

  • 您可以控制请求处理的顺序。
  • 单一责任原则.可以将调用操作的类与执行操作的类解耦。
  • 打开/关闭原则.您可以在不破坏现有客户端代码的情况下将新的处理程序引入应用程序。
  • 有些请求可能最终得不到处理。

与其他模式的关系

  • 责任链命令中介而且观察者处理连接请求发送方和接收方的各种方式:

    • 责任链按顺序将请求沿着潜在接收者的动态链传递,直到其中一个接收者处理它。
    • 命令在发送方和接收方之间建立单向连接。
    • 中介消除发送方和接收方之间的直接连接,迫使它们通过中介对象间接通信。
    • 观察者让接收者动态地订阅和取消订阅接收请求。
  • 责任链常与复合.在这种情况下,当一个叶组件获得一个请求时,它可以将请求通过所有父组件的链传递到对象树的根。

  • 处理程序责任链可以实现为命令.在这种情况下,您可以对同一个上下文对象执行许多不同的操作,这些对象由一个请求表示。

    然而,还有另一种方法,其中请求本身是命令对象。在这种情况下,您可以在链接到一个链中的一系列不同上下文中执行相同的操作。

  • 责任链而且装饰具有非常相似的类结构。这两种模式都依赖于递归组合来通过一系列对象传递执行。然而,有几个关键的区别。

    天哪处理程序可以彼此独立地执行任意操作。它们还可以在任何时候停止进一步传递请求。另一方面,各种各样修饰符可以扩展对象的行为,同时保持它与基本接口的一致。此外,不允许装饰器破坏请求的流程。

代码示例

c#中的责任链c++中的责任链围棋中的责任链Java中的责任链PHP中的责任链Python中的责任链Ruby中的责任链Rust中的责任链Swift中的责任链TypeScript中的责任链

Baidu
map