Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

18_责任链模式 Chain of Responsibility Pattern

1. 基本信息

中文名称: 责任链模式

英文名称: Chain of Responsibility Pattern

模式类型: 行为型设计模式

Rust 中常见实现方式: trait + 包装结构体 + Box/Arc、枚举 + match、闭包链

责任链模式的核心是:将请求沿着处理链传递,多个对象都有机会处理请求,从而避免调用方和处理对象的强耦合。

在 Rust 中,责任链模式通常通过 trait + 包装结构体枚举 + match 实现,也可利用闭包链处理请求。

代码示例


2. 模式核心思想

责任链模式的核心思想是:

将请求沿着处理对象链传递,每个对象判断是否处理该请求,如果不能处理则传递给下一个对象,直到处理完或链末尾。

换句话说:

  • 调用方只发送请求,不关心具体谁处理
  • 每个处理者独立判断是否能处理
  • 请求沿链传递,动态组合处理对象

3. 这个模式解决什么问题

在实际开发中,责任链模式主要解决以下问题:

1. 多个对象可以处理同类请求,调用方无需指定处理者
2. 动态组合处理对象,增加灵活性
3. 避免调用方与具体处理对象的强耦合
4. 可以扩展处理链而不修改调用方代码

典型场景:

  • 请求审批流程:部门主管 → 分管领导 → 总经理
  • 日志系统:Debug → Info → Error → Critical
  • UI 事件处理:事件沿控件层级传递
  • 中间件链:Web 请求经过多个处理器(验证 → 日志 → 缓存 → 路由)

4. 不使用这个模式会怎样

如果不使用责任链模式,可能出现:

1. 调用方需要明确指定具体处理对象
2. 多个处理对象调用耦合度高
3. 新增处理对象需要修改调用方
4. 请求处理逻辑分散,维护成本高

5. 传统面向对象中的实现思路

在传统 OOP 中,责任链模式通常包含三个角色:

1. Handler(处理者接口):定义处理请求方法
2. ConcreteHandler(具体处理者):实现 Handler 接口,并持有下一个处理者
3. Client(调用方):发送请求,不关心处理者

调用流程:

  • Client 调用链头 Handler
  • Handler 判断是否能处理请求
  • 如果不能处理,则调用 next_handler 的 handle 方法
  • 链条一直传递直到被处理或末尾

结构示意:

Handler
 ├── set_next(next: Box<dyn Handler>)
 └── handle(request)
ConcreteHandler
 ├── handle(request) { if can_handle { ... } else { next.handle(request) } }

6. Rust 中的实现思路

6.1 Rust 中通常怎么实现

在 Rust 中,责任链模式通常用 trait + 包装结构体 + Box<dyn Trait> 实现:

// 请求处理接口
trait Handler {
    fn handle(&self, request: &str);
}

// 具体处理者
struct AuthHandler {
    next: Option<Box<dyn Handler>>,
}

impl Handler for AuthHandler {
    fn handle(&self, request: &str) {
        if request == "auth" {
            println!("AuthHandler 处理请求");
        } else if let Some(next) = &self.next {
            next.handle(request);
        }
    }
}

struct LogHandler {
    next: Option<Box<dyn Handler>>,
}

impl Handler for LogHandler {
    fn handle(&self, request: &str) {
        if request == "log" {
            println!("LogHandler 处理请求");
        } else if let Some(next) = &self.next {
            next.handle(request);
        }
    }
}

fn main() {
    let log_handler = LogHandler { next: None };
    let auth_handler = AuthHandler { next: Some(Box::new(log_handler)) };

    auth_handler.handle("auth");
    auth_handler.handle("log");
}

特点:

  • 调用方只发送请求
  • 每个处理者独立判断是否能处理请求
  • 链条可动态组合
  • 可通过 Box<dyn Handler> 支持动态分发,也可用泛型实现静态分发

6.2 和传统 OOP 写法相比有什么不同

  1. Rust 没有类继承,使用 trait + Box/Arc 实现多态。
  2. 静态分发可使用泛型,动态分发使用 Box<dyn Handler>
  3. 所有权机制保证链条引用安全,无悬挂指针。
  4. 不依赖虚函数或继承体系,类型安全,零成本抽象。

6.3 Rust 中是否有更自然的替代写法

  • 简单处理链可使用 闭包链,每个闭包处理请求或传递下一个闭包。
  • 枚举 + match 可替代静态处理链,适合请求类型固定且数量有限。
  • 多线程场景可用 channel +线程,实现异步责任链。

7. Rust 中涉及的语言特性

1. trait
2. impl
3. Box<dyn Trait>(动态分发)
4. Option<Box<dyn Trait>>(链式结构)
5. 所有权和借用
6. Arc / Mutex(多线程安全,可选)
7. 闭包(可选,用于函数链)

8. 性能与工程代价

  • 静态分发(泛型)性能高,调用可内联
  • 动态分发(Box<dyn Trait>)可能有一次虚函数调用开销
  • 链条长度过长可能增加栈调用开销,但通常可忽略
  • 多线程异步链条增加 channel 或锁开销
  • Rust 所有权机制保证内存安全,无运行时 GC

9. 典型应用场景

1. Web 中间件链:身份验证 → 日志 → 缓存 → 路由
2. GUI 事件处理链:控件层级事件传递
3. 审批流程:部门 → 分管领导 → 总经理
4. 日志级别处理链:Debug → Info → Warning → Error
5. 消息处理系统:多个处理者按顺序处理请求

10. 和相似模式的区别

  • 策略模式:策略模式替换整个算法;责任链模式沿链传递请求,多个处理者可依次处理。
  • 观察者模式:观察者模式通知所有依赖对象;责任链模式请求沿链传递,通常只处理一次或部分处理。
  • 装饰器模式:装饰器增强对象功能;责任链模式处理请求沿链传递,不改变原对象。
  • 外观模式:外观提供统一接口简化调用;责任链模式是请求动态沿处理者链传递。

11. 使用该模式的优点

1. 调用方无需关心具体处理对象
2. 可以动态组合处理链
3. 每个处理者独立封装逻辑
4. 松耦合,易于扩展新处理者
5. 可支持异步和多线程处理链

12. 使用该模式的代价

1. 链条过长可能增加调用开销
2. Box<dyn Trait> 动态分发有虚函数调用成本
3. 多线程或异步实现增加同步或消息开销
4. 调试链条逻辑可能较复杂

13. 什么时候不应该使用

1. 请求处理对象数量固定且少
2. 调用方只需处理单个对象
3. 链条组合和动态传递不必要
4. 系统简单,不需要松耦合

14. 一个简单例子思路

场景: Web 请求中间件链

  • Subject:请求对象
  • Handler:AuthenticationHandler → LoggingHandler → CacheHandler → RouteHandler
  • 请求沿链传递,每个 Handler 判断是否处理
  • 调用方发送请求,无需知道具体 Handler

适合练习 Rust 特性:trait、Box<dyn Trait>OptionArc/Mutex(多线程)、闭包链。


15. 总结一句话

责任链模式的本质是:将请求沿处理者链传递,每个对象独立判断是否处理,从而实现松耦合和动态扩展。在 Rust 中,trait + 包装结构体 + Box/Option 或闭包链是常用实现方式。