> For the complete documentation index, see [llms.txt](https://l1nwatch.gitbook.io/python-design-mode/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://l1nwatch.gitbook.io/python-design-mode/di-san-bu-fen-hang-wei-xing-mo-shi/di-10-zhang-ze-ren-lian-mo-shi.md).

# 第 10 章 责任链模式

开发一个应用时，多数时候我们都能预先知道哪个方法能处理某个特定请求。然而，情况并非总是如此。

责任链（Chain of Responsibility）模式用于让多个对象来处理单个请求时，或者用于预先不知道应该由哪个对象（来自某个对象链）来处理某个特定请求时。其原则如下所示：

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

## 10.1 现实生活的例子

ATM 机以及及一般而言用于接收/返回钞票或硬币的任意类型机器（比如，零食自动贩卖机）都使用了责任链模式。

## 10.2 软件的例子

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

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

## 10.3 应用案例

通过使用责任链模式，我们能让许多不同对象来处理一个特定请求。在我们预先不知道应该由哪个对象来处理某个请求时，这是有用的。其中一个例子是采购系统。

另一个责任链可以派上用场的场景是，在我们知道可能会有多个对象都需要对同一个请求进行处理之时。这在基于事件的编程中是常有的事情。

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

## 10.4 实现

使用 Python 实现责任链模式有许多种方式，Vespe 的实现以地道的 Python 风格使用动态分发来处理请求。

参考实现一个简单的事件系统：

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

# 核心类
class Widget:
    # 注意 parent=None 表明默认继承关系为空
    def __init__(self, parent=None):
        self.parent = parent
    # Widget 与 Event 仅是关联关系，表明 Widget 类知道 Event 类，但对其没有严格的引用，只需要作为参数传递即可
    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)

# 具有不同行为的控件 1
class MainWindow(Widget):
    def handle_close(self, event):
        print('MainWindow: {}'.format(event))
    def handle_default(self, event):
        print('MainWindow Default: {}'.format(event))

# 具有不同行为的控件 2
class SendDialog(Widget):
    def handle_paint(self, event):
        print('SendDialog: {}'.format(event))

# 具有不同行为的控件 3
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('\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()
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://l1nwatch.gitbook.io/python-design-mode/di-san-bu-fen-hang-wei-xing-mo-shi/di-10-zhang-ze-ren-lian-mo-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
