美文网首页
python设计模式11责任链(Chain of Respons

python设计模式11责任链(Chain of Respons

作者: python测试开发 | 来源:发表于2021-07-13 17:09 被阅读0次

在开发应用程序时,大多数时候我们都知道哪个方法应该提前满足某个特定的请求。然而,情况并不总是这样的。例如,想想任何广播计算机网络,如原始的以太网实现。在广播计算机网络中,所有的请求都被发送到所有的节点(为了简单起见,广播域被排除在外),但只有对所发请求感兴趣的节点才会处理它。

所有参与广播网络的计算机都是通过共同的媒介(如连接所有节点的电缆)相互连接的。如果一个节点不感兴趣或不知道如何处理请求,它可以执行以下行动。

  • 忽略该请求,什么也不做
  • 将请求转发给下一个节点

当我们想给多个对象机会来满足一个请求时,或者当我们事先不知道哪个对象(来自一个对象链)应该处理一个特定的请求时,就会用到责任链模式。

  • 首先向链上的第一个对象发送一个请求
  • 该对象决定它是否应该满足该请求
  • 该对象将请求转发给下一个对象
  • 这个过程重复进行,直到我们到达链的末端。

客户端代码只知道第一个处理元素,而不是对所有处理元素的引用,每个处理元素只知道其紧邻的下一个邻居(称为继承者),而不知道其他每个处理元素。这通常是一种单向的关系,在编程方面,这意味着单链列表与双链列表的对比;单链列表不允许双向导航,而双链列表则允许。这种链式组织的使用是有原因的。它实现了发送者(客户端)和接收者(处理元素)之间的解耦。

现实世界的例子

在软件中,Java的servlet过滤器是在HTTP请求到达目标之前执行的代码片段。在使用servlet过滤器时,有一连串的过滤器。每个过滤器执行不同的动作(用户认证、记录、数据压缩等等),并将请求转发给下一个过滤器,直到链子用完为止,或者在出现错误时中断流程--例如,认证连续三次失败。

苹果的Cocoa和Cocoa Touch框架,使用责任链来处理事件。当视图收到不知道如何处理的事件时,它会把事件转发给它的超级视图。这样一直持续到一个视图有能力处理该事件或视图链用完为止。

应用

通过使用责任链模式,我们为一些不同的对象提供机会来满足特定的请求。当我们不知道哪个对象应该事先满足一个请求时,这很有用。比如采购系统。在采购系统中,有许多审批机构。一个审批机构可能能够批准一定价值以内的订单,比方说100美元。如果订单的金额超过100美元,该订单就会被送到链上的下一个审批机构,该机构可以审批高达200美元的订单,以此类推。

责任链有用的另一种情况是,当我们知道一个以上的对象可能需要处理一个请求时。这就是基于事件的编程中的情况。一个单一的事件,比如鼠标左键,可以被一个以上的监听器捕获。

需要注意的是,如果所有的请求都可以由一个处理元素来处理,那么责任链模式就不是很有用,除非我们真的不知道是哪个元素。这种模式的价值在于它所提供的解耦性。客户端和所有处理元素之间不是多对多的关系(处理元素和所有其他处理元素之间的关系也是如此),而是只需要知道如何与链的起点(头部)通信。

下图说明了紧耦合和松耦合的区别。松散耦合系统背后的想法是简化维护,使我们更容易理解它们的功能。

实现

有很多方法可以在Python中实现责任链,但我最喜欢的实现是Vespe Savikko。Vespe的实现使用Pythonic风格的动态调度来处理请求(http://j.mp/ddispatch)。

让我们以Vespe的实现为指导,实现一个简单的、基于事件的系统。下面是该系统的UML类图。

class Event: 
    def __init__(self, name): 
        self.name = name 

    def __str__(self): 
        return self.name 

class Widget: 
    def __init__(self, parent=None): 
        self.parent = parent 

    def handle(self, event): 
        handler = f'handle_{event}' 
        if hasattr(self, handler): 
            method = getattr(self, handler) 
            method(event) 
        elif self.parent is not None: 
            self.parent.handle(event) 
        elif hasattr(self, 'handle_default'): 
            self.handle_default(event) 

class MainWindow(Widget): 
    def handle_close(self, event): 
        print(f'MainWindow: {event}') 

    def handle_default(self, event): 
        print(f'MainWindow Default: {event}') 

class SendDialog(Widget): 
    def handle_paint(self, event): 
        print(f'SendDialog: {event}') 

class MsgText(Widget): 
    def handle_down(self, event): 
        print(f'MsgText: {event}') 

def main(): 
    mw = MainWindow() 
    sd = SendDialog(mw) 
    msg = MsgText(sd) 

    for e in ('down', 'paint', 'unhandled', 'close'): 
        evt = Event(e) 
        print(f'Sending event -{evt}- to MainWindow') 
        mw.handle(evt) 
        print(f'Sending event -{evt}- to SendDialog') 
        sd.handle(evt) 
        print(f'Sending event -{evt}- to MsgText') 
        msg.handle(evt) 

if __name__ == '__main__': 
    main()

事件类描述了事件。我们将保持简单,所以在我们的案例中,一个事件只有一个名字。

Widget类是应用程序的核心类。在UML图中显示的父集合表明每个Widget可以有一个对父对象的引用,按照惯例,我们假定它是一个Widget实例。然而,请注意,根据继承规则,Widget的任何子类的实例(例如,MsgText的实例)也是Widget的实例。parent的默认值是None。

handle()方法通过hasattr()和getattr()使用动态调度来决定谁是一个特定请求(事件)的处理者。如果被要求处理一个事件的部件不支持它,有两个回退机制。如果该部件有一个父部件,那么父部件的 handle() 方法被执行。如果该小组件没有父级但有handle_default()方法,则执行handle_default()。

在这一点上,你可能已经意识到为什么Widget和Event类在UML类图中只有关联(没有聚合或组合关系)。关联是用来表明Widget类知道Event类,但对它没有任何严格的引用,因为一个事件只需要作为参数传递给handle()。

MainWIndow、MsgText和SendDialog都是具有不同行为的窗口部件。并非所有这三个窗口部件都要能处理相同的事件,即使它们能处理相同的事件,它们的行为也可能不同。MainWindow只能处理关闭和默认事件。

main()函数展示了我们如何创建一些小部件和事件,以及小部件如何对这些事件做出反应。所有的事件都被发送到所有的小部件上。注意每个widget的父子关系。sd对象(SendDialog的一个实例)的父对象是mw对象(MainWindow的一个实例)。然而,并不是所有的对象都需要有一个父对象。

在输出中我们可以看到一些有趣的东西。例如,向MainWindow发送一个down事件,最终被默认的MainWindow处理程序处理了。另一个很好的例子是,尽管关闭事件不能被SendDialog和MsgText直接处理,但所有的关闭事件最终都被MainWindow正确处理了。这就是使用父子关系作为回退机制的好处。

如果你想在事件的例子上花更多的创造性时间,你可以替换掉那些愚蠢的打印语句,为列出的事件添加一些实际的行为。当然,你并不局限于所列的事件。只要添加你喜欢的事件,并让它做一些有用的事情就可以了

小结

在本章中,我们介绍了责任链设计模式。当处理程序的数量和类型事先并不清楚时,这种模式对于建模请求和/或处理事件是非常有用的。适合使用责任链的系统的例子是基于事件的系统、采购系统和运输系统。

在责任链模式中,发送者可以直接访问责任链的第一个节点。如果第一个节点不能满足请求,它就把请求转发给下一个节点。这种情况一直持续到请求被某个节点满足或整个链条被穿越为止。这种设计被用来实现发送方和接收方之间的松散耦合。

ATM是责任链的一个例子。用于所有纸币的单一插槽可以被认为是链的头部。从这里开始,根据不同的交易,一个或多个贮藏器被用来处理交易。贮藏器可以被认为是链的处理元素。

Java的servlet过滤器使用责任链模式对HTTP请求进行不同的操作(例如,压缩和认证)。苹果的Cocoa框架使用同样的模式来处理事件,如按下按钮和手指的手势。

相关文章

网友评论

      本文标题:python设计模式11责任链(Chain of Respons

      本文链接:https://www.haomeiwen.com/subject/jojspltx.html