python 实现 责任链模式

本文的目录地址

本文的代码地址

开发一个应用时,多数时候我们都能预先知道哪个方法能处理某个特定请求。然而,情况并非总是如此。例如,在广播计算机网络中,会将请求发送给所有节点,但仅对所发送内容感兴趣的节点会处理请求。


如果一个节点对某个请求不感兴趣或者不知道该如何处理这个请求,可以执行以下两个操作。

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

节点对一个请求的反应方式是实现的细节。然而,我们可以使用广播计算机网络来类比理解责任链模式是什么。责任链(Chain of Responsibility)模式用于让多个对象来处理单个请求时,或者用于预先不知道应该由哪个对象(来自某个对象链)来处理某个特定请求时。其原则如下

  • 存在一个对象链(链表/树或任何其他便捷的数据结构)。
  • 我们一开始将请求发送给链中的第一个对象。
  • 对象决定其是否要处理该请求。
  • 对象将请求转发给下一个对象。
  • 重复该过程,直到到达链尾。

软件的例子

Java的servlet过滤器是在一个HTTP请求到达目标处理程序之前执行的一些代码片段。在使用servlet过滤器时,有一个过滤器链,其中每个过滤器执行一个不同动作(用户身份验证、记日志、数据压缩等),并且将请求转发给下一个过滤器知道链结束;如果发生错误(例如,连续三次身份验证失败)则跳出处理流程。

Apple的Cocoa和Cocoa Touch框架使用责任链来处理事件。在某个视图接收到一个其并不知道如何处理的事件时,会将事件转发给其超视图,直到有个视图能够处理这个事件或者视图链结束。

应用案例

通过使用责任链模式,我们能让许多不同对象来处理一个特定请求。在我们预先不知道应该由哪个对象来处理请求时,这是有用的。其中一个例子是采购系统。在采购系统中,有许多核准权限。某个核准权限可以核准在一定额度之内的订单,假设为100美元。如果订单超过了100美元,则会将订单发送给链中的下一个核准权限,比如能够核准在200美元以下的订单,等等。

另一个责任链例子,单个事件,比如一次鼠标点击,可被多个事件监听者捕获。

不过应该注意,如果所有请求都能被单个处理程序处理,责任链就没用了,除非确实不知道会是哪个程序处理请求。这一模式的价值在于解耦。客户端与所有处理程序之间不再是多对多的关系,客户端仅需要知道如何与链的起始节点进行通信。

实现

这里将会实现一个简单的事件系统。下面是该系统的UML类图。


Event类描述一个事件。为了让它简单一点,在我们的案例中一个事件只有一个name属性。

class Event:
    def __init__(self,name):
        self.name=name
        
    def __str__(self):
        return self.name
Widget类是应用的核心类。UML图中展示的parent聚合关系表明每个控件都有一个到父对象的引用。按照约定,我们假设父对象是一个Widget实例。
class Widget:
    def __init__(self,parent=None):
        self.parent=parent

handle()方法使用动态分发,通过hasattr()和getattr()决定一个特定请求(event)应该由谁来处理。如果被请求处理事件的控件并不支持该事件,则有两种回退机制。如果控件有parent,则执行parent的handle()方法。如果控件没有parent,但有handle_default()方法,则执行handle_default()方法。

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

此时,你可能已明白为什么UML类图中Widget和Event类仅是关联关系而已(不是聚合或组合关系)。关联关系用于表明Widget类知道Event类,但对其没有任何严格的引用,因为事件仅需要作为参数传递给handle()。

MainWindow、MsgText和SendDialog是具有不同行为的控件。我们并不期望这三个控件都能处理相同的事件,即使它们能处理相同事件,表现出来也可能是不同的。MainWindow仅能处理close和default事件。

class MainWidow(Widget):
    def handle_close(self,event):
        print('MainWindow: {}'.format(event))
        
    def handle_default(self,event):
        print('MainWindow: {}'.format(event))

SendDialog仅能处理paint事件。

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

最后,MsgText只能处理down事件。

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

main()函数展示如何创建一些控件和事件,以及控件如何对那些事件做出反应。所有事件都会被发送给所有控件。注意其中每个控件的父子关系。sd对象(SendDialog的一个实例)的父对象是mw(MainWindow的一个实例)。然而,并不是所有对象都需要一个MainWindow实例的父对象。例如,msg对象是以sd作为父对象。

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

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

完整代码文件chain.py,运行结果如下

Sending event -down- to MainWindow
MainWindow: down
Sending event -down- to SendDialog
MainWindow: down
Sending event -down- to MsgText
MsgText: down

Sending event -paint- to MainWindow
MainWindow: paint
Sending event -paint- to SendDialog
SendDialog: paint
Sending event -paint- to MsgText
SendDialog: paint

Sending event -unhandled- to MainWindow
MainWindow: unhandled
Sending event -unhandled- to SendDialog
MainWindow: unhandled
Sending event -unhandled- to MsgText
MainWindow: unhandled

Sending event -close- to MainWindow
MainWindow: close
Sending event -close- to SendDialog
MainWindow: close
Sending event -close- to MsgText
MainWindow: close
从输出中我们能看到一些有趣的东西。例如,发送一个down事件给MainWindow,最终被MainWindow默认处理函数处理。另一个不错的用例是,虽然close事件不能被SendDialog和MsgText直接处理,但所有close事件最终都能被MainWindow正确处理。这正是使用父子关系作为一种回退机制的优美之处。


猜你喜欢

转载自blog.csdn.net/hbu_pig/article/details/80799488