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 写法相比有什么不同
- Rust 没有类继承,使用 trait + Box/Arc 实现多态。
- 静态分发可使用泛型,动态分发使用
Box<dyn Handler>。 - 所有权机制保证链条引用安全,无悬挂指针。
- 不依赖虚函数或继承体系,类型安全,零成本抽象。
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>、Option、Arc/Mutex(多线程)、闭包链。
15. 总结一句话
责任链模式的本质是:将请求沿处理者链传递,每个对象独立判断是否处理,从而实现松耦合和动态扩展。在 Rust 中,trait + 包装结构体 + Box/Option 或闭包链是常用实现方式。