Python设计模式之职责链模式(11)

职责链模式是一种行为设计模式,用于创建链式对象来处理请求,请求在链上逐个传递,直到被处理。模式降低了请求发送者与接收者的耦合,允许动态指定请求处理者。适用场景包括需要多个对象按顺序处理请求的情况,如在线订购系统的权限验证和事件驱动系统。使用步骤包括声明处理者接口、创建处理者子类、组装链和触发请求。此模式可能导致请求处理不确定性和调试困难。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

职责链模式(Chain of Responsibility Pattern):创建链式对象用来接收广播消息。

为请求创建了一个接收者对象的链,用来依次处理消息,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

职责链模式的定义如下:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

需要说明的是,责任链模式中的应该只有一个处理者,也就是说,本例中的“最终批准”为该对象所谓的“请求处理”。

 

1 介绍

 

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

优点: 

  • 降低耦合度。它将请求的发送者和接收者解耦。
  • 简化了对象。使得对象不需要知道链的结构。
  • 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  • 增加新的请求处理类很方便。

缺点: 

  • 不能保证请求一定被接收。
  • 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
  • 可能不容易观察运行时的特征,有碍于除错。

 

2 适用场景

若一个请求可能由一个对请求有链式优先级的处理群所处理时,可以考虑职责链模式。

主要适用场景:

  • 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用职责链模式。

该模式能将多个处理者连接成一条链。接收到请求后,它会“询问”每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。

  • 当必须按顺序执行多个处理者时,可以使用该模式。

无论你以何种顺序将处理者连接成一条链,所有请求都会严格按照顺序通过链上的处理者。

  • 如果所需处理者及其顺序必须在运行时进行改变,可以使用职责链模式。

如果在处理者类中有对引用成员变量的设定方法,你将能动态地插入和移除处理者,或者改变其顺序。

 

职责链(Chain of Responsibility)模式用于让多个对象来处理单个请求时,或者用于预先不知道应该由哪个对象(来自某个对象链)来处理某个特定请求时。其原则如下:

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

经典使用场景:

(1)采购系统

假如你正在开发一个在线订购系统。你希望对系统访问进行限制,只允许认证用户创建订单。

此外,拥有管理权限的用户也拥有所有订单的完全访问权限。

在接下来的几个月里,你实现了后续的几个检查步骤。

  • 一位同事认为直接将原始数据传递给订购系统存在安全隐患。因此你新增了额外的验证步骤来清理请求中的数据。

  • 过了一段时间,有人注意到系统无法抵御暴力密码破解方式的攻击。为了防范这种情况,你立刻添加了一个检查步骤来过滤来自同一 IP 地址的重复错误请求。

  • 又有人提议你可以对包含同样数据的重复请求返回缓存中的结果,从而提高系统响应速度。因此,你新增了一个检查步骤,确保只有没有满足条件的缓存结果时请求才能通过并被发送给系统。

处理过程:

在上述示例中,每个检查步骤都可被抽取为仅有单个方法的类,并执行检查操作。请求及其数据则会被作为参数传递给该方法。

这些处理者连成一条链。链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。除了处理请求外,处理者还负责沿着链传递请求。请求会在链上移动,直至所有处理者都有机会对其进行处理。

最重要的是:处理者可以决定不再沿着链传递请求,这可高效地取消所有后续处理步骤。

在我们的订购系统示例中,处理者会在进行请求处理工作后决定是否继续沿着链传递请求。如果请求中包含正确的数据,所有处理者都将执行自己的主要行为,无论该行为是身份验证还是数据缓存。

(2)事件驱动

处理者接收到请求后自行决定是否能够对其进行处理。如果自己能够处理,处理者就不再继续传递请求。因此在这种情况下,每个请求要么最多有一个处理者对其进行处理,要么没有任何处理者对其进行处理。在处理图形用户界面元素栈中的事件时,这种方式非常常见。

例如,当用户点击按钮时,按钮产生的事件将沿着 GUI 元素链进行传递,最开始是按钮的容器(如窗体或面板),直至应用程序主窗口。链上第一个能处理该事件的元素会对其进行处理。此外,该例还有另一个值得我们关注的地方:它表明我们总能从对象树中抽取出链来。

所有处理者类均实现同一接口是关键所在。每个具体处理者仅关心下一个包含execute(执行)方法的处理者。这样一来,你就可以在运行时使用不同的处理者来创建链,而无需将相关代码与处理者的具体类进行耦合


3 使用步骤

职责链模式常用代码结构如下:

使用步骤:

(1)声明处理者接口并描述请求处理方法的签名。

确定客户端如何将请求数据传递给该方法。最灵活的方式是将请求转换为对象,然后将其以参数的形式传递给处理函数。

(2)为了在具体处理者中消除重复的样本代码,你可以根据处理者接口创建抽象处理者基类。

该类需要有一个成员变量来存储指向链上下个处理者的引用。你可以将其设置为不可变类。但如果你打算在运行时对链进行改变,则需要定义一个设定方法来修改引用成员变量的值。

(3)依次创建具体处理者子类并实现其处理方法。

每个处理者在接收到请求后都必须做出两个决定:

  • 是否自行处理这个请求。
  • 是否将该请求沿着链进行传递。

(4)客户端可以自行组装链,或者从其他对象处获得预先组装好的链。在后一种情况下,你必须实现工厂类以根据配置或环境设置来创建链。

(5)客户端可以触发链中的任意处理者,而不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递,或者请求到达链尾。

(6)由于链的动态性,客户端需要准备好处理以下情况:

  • 链中可能只有单个链接。
  • 部分请求可能无法到达链尾。
  • 其他请求可能直到链尾都未被处理。

 

4 示例代码

示例一:实现一个流水线系统,自动分类食物

from abc import ABC, abstractmethod
from typing import Any, Optional

#步骤一:定义抽象接口
class Handler(ABC):
    """
    Handler interface,
    """
    @abstractmethod
    def set_next(self, handler):
        pass

    def handle(self, request) -> Optional[str]:
        pass

#步骤二:定义默认hander,以及默认handle方法,request默认流向下一个对象handle
class AbstractHandler(Handler):
    """
    Handler Base方法,主要定义Hander默认handle方法
    """
    _next_hander = None

    def set_next(self, handler):
        self._next_hander = handler
        """
        指向职责链的下一个handler链。便于client定义职责链处理顺序
        """
        return handler

    def handle(self, request: Any):
        if self._next_hander:
            return self._next_hander.handle(request)
        return None

#步骤三:定义具体的业务handle,处理request或者流转到下一个对象
class MonkeyHandler(AbstractHandler):
    def handle(self, request: Any):
        if request == "Banana":
            return f"Monkey 喜欢吃 {request}"
        else:
            return super().handle(request)

class CatHandler(AbstractHandler):
    def handle(self, request: Any):
        if request == "Fish":
            return f"Cat 喜欢吃 {request}"
        else:
            return super().handle(request)

class DogHandler(AbstractHandler):
    def handle(self, request: Any):
        if request == "Bone":
            return f"Dog 喜欢吃 {request}"
        else:
            return super().handle(request)

def client(handler):
    for food in ["Fish", "Banana", "Bone", "Coffee"]:
        result = handler.handle(food)
        if result:
            print(result)
        else:
            print(f"都不吃这个食物:{food}")

def main():
    monkey = MonkeyHandler()
    cat = CatHandler()
    dog = DogHandler()

    #定义责任链处理顺序monkey->cat->dog
    monkey.set_next(cat).set_next(dog)
    print("责任链:monkey->cat->dog")
    client(monkey)

if __name__ == "__main__":
    main()

运行结果:

责任链:monkey->cat->dog
Cat 喜欢吃 Fish
Monkey 喜欢吃 Banana
Dog 喜欢吃 Bone
都不吃这个食物:Coffee
示例二:事件驱动系统
class Event(object):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

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

    """
    def __init__(self, parent=None):
        self.parent = parent

    def handle(self, event):
        handler = f'handel_{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():
    """
    main()函数展示如何创建一些控件和事件,以及控件如何对那些事件作出反应。所有事件都会被发送给所有控件。
    注意其中每个控件的父子关系。sd对象(SendDialog的一个实例)的父对象是mw(MainWindow的一个实例)。
    然而,并不是所有对象都需要一个MainWindow实例的父对象。例如,msg对象(MsgText的一个实例)是以sd作为父对象。

    """
    mw = MainWindow()
    sd = SendDialog(mw)    # parent是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)

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
MainWindow Default: down

Sending event -paint- to MainWindow
MainWindow Default: paint
Sending event -paint- to SendDialog
MainWindow Default: paint
Sending event -paint- to MsgText
MainWindow Default: 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 Default: close
Sending event -close- to SendDialog
MainWindow Default: close
Sending event -close- to MsgText
MainWindow Default: close

 

5 与其他模式关系

职责链命令中介者观察者用于处理请求发送者和接收者之间的不同连接方式:

  • 职责链​按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
  • 命令​在发送者和请求者之间建立单向连接。
  • 中介者​清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
  • 观察者​允许接收者动态地订阅或取消接收请求。

职责链通常和组合模式结合使用。在这种情况下,叶组件接收到请求后,可以将请求沿包含全体父组件的链一直传递至对象树的底部。

职责链的管理者可以使用命令模式来实现。在这种情况下,你可以对由请求代表的同一个上下文对象执行许多不同的操作。

还有另外一种实现方式,那就是请求自身就是一个​命令​对象。在这种情况下,你可以对由一系列不同上下文连接而成的链执行相同的操作。

职责链装饰模式的类结构非常相似。两者都依赖递归组合将需要执行的操作传递给一系列对象。但是,两者有几点重要的不同之处。

职责链的管理者可以相互独立地执行一切操作,还可以随时停止传递请求。另一方面,各种​装饰​可以在遵循基本接口的情况下扩展对象的行为。此外,装饰无法中断请求的传递。

 

参考文献:

https://www.jianshu.com/nb/10025405

https://refactoringguru.cn/design-patterns/chain-of-responsibility

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值