zoukankan      html  css  js  c++  java
  • python设计模式之责任链模式

    python设计模式之责任链模式

    开发一个应用时,多数时候我们都能预先知道哪个方法能处理某个特定请求。然而,情况并非总是如此。例如,想想任意一种广播计算机网络,例如最早的以太网实现。在广播计算机网络中,会将所有请求发送给所有节点(简单起见,不考虑广播域),但仅对所发送请求感兴趣的节点会处理请求。加入广播网络的所有计算机使用一种常见的媒介相互连接。

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

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

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

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

    这通常是一种单向关系,用编程术语来说是一个单向链表,与之相反的是双向链表。单向链表不允许双向地遍历元素,双向链表则是允许的。这种链式组织方式大有用处:可以解耦发送方(客户端)和接收方(处理元素)。

    1. 现实生活中的例子

    ATM机以及及一般而言用于接收/返回钞票或硬币的任意类型机器(比如,零食自动贩卖机)都使用了责任链模式,机器上总会有一个放置各种钞票的槽口。

    钞票放入之后,会被传递到恰当的容器。钞票返回时,则是从恰当的容器中获取。我们可以把这个槽口视为共享通信媒介,不同的容器则是处理元素。结果包含来自一个或多个容器的现金。

    2. 软件的例子

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

    3. 应用案例

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

    另一个责任链可以派上用场的场景是,在我们知道可能会有多个对象都需要对同一个请求进行处理之时。这在基于事件的编程中是常有的事情。单个事件,比如一次鼠标左击,可被多个事件监听者捕获。

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

    4. 实现

    我们以Vespe的实现为参考实现一个简单的事件系统。

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

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

    Widget类是应用的核心类。按照约定,我们假定父对象是一个Widget实例。然而,注意,根据继承的规则,任何Widget子类的实例(例如, MsgText的实例)也是Widget实例。 parent的默认值为None。

    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)
    

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

    class MainWindow(Widget):
        def handle_close(self, event):
        	print('MainWindow: {}'.format(event))
        def handle_default(self, event):
        	print('MainWindow Default: {}'.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对象( MsgText的一个实例)是以sd作为父对象。

    def main():
        mw = MainWindow()
        sd = SendDialog(mw)
        msg = MsgText(sd)
        for e in ('down', 'paint', 'unhandled', 'close'):
            evt = Event(e)
            print('
    Sending 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)
    

    以下是示例的完整代码。

    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 = '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)
    class MainWindow(Widget):
        def handle_close(self, event):
        	print('MainWindow: {}'.format(event))
        def handle_default(self, event):
        	print('MainWindow Default: {}'.format(event))
    class SendDialog(Widget):
        def handle_paint(self, event):
        	print('SendDialog: {}'.format(event))
    class MsgText(Widget):
        def handle_down(self, event):
        	print('MsgText: {}'.format(event))
    def main():
        mw = MainWindow()
        sd = SendDialog(mw)
        msg = MsgText(sd)
        for e in ('down', 'paint', 'unhandled', 'close'):
            evt = Event(e)
            print('
    Sending 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)
    if __name__ == '__main__':
    	main()
    

    输出结果:

    Sending event -down- to MainWindow
    MainWindow Default: down
    Sending event -down- to SendDialog
    MainWindow Default: down
    Sending event -down- to MsgText
    MsgText: down
    Sending event -paint- to MainWindow
    MainWindow Default: paint
    Sending event -paint- to SendDialog
    SendDialog: paint
    Sending event -paint- to MsgText
    SendDialog: paint
    Sending event -unhandled- to MainWindow
    MainWindow Default: unhandled
    Sending event -unhandled- to SendDialog
    MainWindow Default: unhandled
    Sending event -unhandled- to MsgText
    MainWindow Default: 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正确处理。这正是使用父子关系作为一种回退机制的优美之处。

    5. 小结

    在责任链模式中,发送方可直接访问链中的首个节点。若首个节点不能处理请求,则转发给下一个节点,如此直到请求被某个节点处理或者整个链遍历结束。这种设计用于实现发送方与接收方(多个)之间的解耦。

  • 相关阅读:
    Cnic.SafeNativeMethods
    KnockOut文档--模板绑定
    luoguP1120 小木棍 [数据加强版]
    luoguP1951 收费站_NOI导刊2009提高(2)
    luoguP1821 [USACO07FEB]银牛派对Silver Cow Party
    luoguP2991 [USACO10OPEN]水滑梯Water Slides
    luoguP4198 楼房重建
    (数位dp)吉利数字 区间k大
    数字游戏
    Amount of Degrees
  • 原文地址:https://www.cnblogs.com/JonnyJiang-zh/p/13234700.html
Copyright © 2011-2022 走看看