责任链模式:解耦请求发送者与接收者

FreeGuideOnline 最新 2026-06-18

责任链模式:概述与动机

责任链模式(Chain of Responsibility)是一种行为设计模式,它允许你将请求沿着一条由多个处理器组成的链进行传递,直到某个处理器处理该请求为止。通过这种方式,请求的发送者与接收者被彻底解耦——发送者无需知道哪个对象最终会处理请求,链的结构和处理逻辑也可以动态调整。

在日常开发中,经常会出现“需要多个对象尝试处理同一请求”的场景,例如:

  • 图形界面中的事件冒泡:一个按钮点击可能被按钮自身、其父容器、再到顶层窗口处理。
  • Web 框架中的中间件:请求依次经过认证、日志、压缩等中间件。
  • 多级审批流程:员工请假依次提交给直属领导、部门经理、人事总监。

如果将这些处理器硬编码集中到一个类中,系统会高度耦合、难以扩展。责任链模式通过将每个处理器独立成对象,并用一条链串联它们,完美解决了这个问题。


责任链模式的结构

一个典型的责任链模式由以下角色构成:

角色 说明
Handler(处理器接口或抽象类) 定义处理请求的抽象方法,通常还提供一个方法用于设置下一个处理器。
ConcreteHandler(具体处理器) 实现处理请求的逻辑,如果能处理则处理,否则将请求转发给下一个处理器。
Client(客户端) 创建链,并向链的起点提交请求。

结构图示(逻辑模型)

Client ──> Handler A ──> Handler B ──> Handler C ──> null
              │              │              │
          可处理?        可处理?        可处理?

每个处理器持有下一个处理器的引用,形成单向链表。请求沿链传递,直到某个处理器决定消费它(不再继续传递),或到达链尾未被处理。


责任链模式的核心优势:解耦请求发送者与接收者

传统写法中,请求的发送者必须显式调用特定接收者的方法,例如:

if (authService.authenticate(request)) {
    logService.log(request);
    dataService.process(request);
}

这种代码违背开闭原则:每新增一个处理步骤,都需要修改客户端代码,且客户端必须了解所有潜在接收者。责任链模式将请求发送者从这种混乱中解放出来,客户端只需将请求发给链头,后续的处理逻辑完全由链上的对象自行决定。这带来了真正的发送者-接收者解耦


责任链模式的标准实现

以下以 Java 语言为例,展示一个简单的责任链模式实现,模拟员工请假审批流程。

1. 定义处理器抽象类

public abstract class LeaveHandler {
    protected LeaveHandler next;
    
    // 设置下一个处理器
    public LeaveHandler setNext(LeaveHandler next) {
        this.next = next;
        return next; // 返回next以便链式调用
    }
    
    // 处理请求的抽象方法
    public abstract void handleRequest(LeaveRequest request);
}

2. 定义请求类

public class LeaveRequest {
    private String employeeName;
    private int leaveDays;
    
    // 构造器、getter、setter 省略
}

3. 实现具体处理器

// 直属领导:只能批准3天以内假期
public class DirectLeader extends LeaveHandler {
    @Override
    public void handleRequest(LeaveRequest request) {
        if (request.getLeaveDays() <= 3) {
            System.out.println("直属领导批准了 " + 
                request.getEmployeeName() + " 的请假");
        } else if (next != null) {
            next.handleRequest(request);
        }
    }
}

// 部门经理:只能批准7天以内假期
public class DeptManager extends LeaveHandler {
    @Override
    public void handleRequest(LeaveRequest request) {
        if (request.getLeaveDays() <= 7) {
            System.out.println("部门经理批准了 " + 
                request.getEmployeeName() + " 的请假");
        } else if (next != null) {
            next.handleRequest(request);
        }
    }
}

// 人事总监:可以批准任何天数
public class HRDirector extends LeaveHandler {
    @Override
    public void handleRequest(LeaveRequest request) {
        System.out.println("人事总监批准了 " + 
            request.getEmployeeName() + " 的请假");
    }
}

4. 客户端组装链并使用

public class Client {
    public static void main(String[] args) {
        LeaveHandler directLeader = new DirectLeader();
        LeaveHandler deptManager = new DeptManager();
        LeaveHandler hrDirector = new HRDirector();
        
        // 构建链: 直属领导 -> 部门经理 -> 人事总监
        directLeader.setNext(deptManager).setNext(hrDirector);
        
        LeaveRequest req1 = new LeaveRequest("张三", 2);
        LeaveRequest req2 = new LeaveRequest("李四", 5);
        LeaveRequest req3 = new LeaveRequest("王五", 15);
        
        directLeader.handleRequest(req1); // 直属领导批准
        directLeader.handleRequest(req2); // 部门经理批准
        directLeader.handleRequest(req3); // 人事总监批准
    }
}

关键点:客户端只与 directLeader 交互,后续审批流程对客户端完全透明。增加一个新审批层级(如副总经理)时,只需添加一个新的具体处理器,并插入到链中,无需修改客户端代码。


常见变体与实现细节

1. 如何在链中停止传递?

有些需求要求请求被处理后就不再向下传递。在上例中,每个处理器在处理后没有调用 next,自然就停止了。某些设计会要求处理器始终尝试处理,并且无论是否处理都继续传递(类似过滤链)。你需要根据业务决定传递规则。

2. 责任链与纯“洋葱模型”

  • 洋葱模型(如 Koa 中间件):请求从外向内穿过所有中间件,再由内向外响应,每个中间件可做前后置处理。
  • 单向链:请求被处理后即终止,或即使未处理也依次尝试。责任链模式更偏向单向链。

3. 使用函数式编程简化

在支持函数式编程的语言中,责任链可由一连串函数组合实现:

List<Consumer<LeaveRequest>> handlers = Arrays.asList(
    req -> { if (req.getLeaveDays() <= 3) ... },
    req -> { if (req.getLeaveDays() <= 7) ... }
);
// 遍历 handlers 直到某个处理并 break,或结合 Optional 实现

尽管灵活,但丢失了“可动态增删链节点”的面向对象优势。


适用场景

当你的代码中有大量顺序依赖的条件处理逻辑时,考虑使用责任链模式:

  • 多个对象可能处理同一请求,具体由运行时条件决定
  • 需要向一组对象提交请求,而不想显式指定接收者
  • 需要动态指定处理者集合(例如系统根据配置加载不同的校验插件)。

典型案例

  • Java 的 Filter 链、Servlet 的过滤逻辑。
  • Spring Security 的安全过滤器链。
  • Netty 的 ChannelPipeline
  • 日志框架(如 Log4j)的日志处理器链。

责任链模式的优缺点

优点

  • 发送者与接收者解耦,发送者无需知道哪个对象处理请求。
  • 增强系统的可扩展性,可以动态添加或修改链,符合开闭原则。
  • 可灵活控制处理顺序,通过改变串联顺序即可调整。
  • 简化对象,每个处理器只关注自己的逻辑,无需关心其他处理者。

缺点

  • 请求可能到达链尾仍未被处理(需提供默认处理或告警机制)。
  • 如果链过于长,请求遍历可能影响性能(但通常可忽略)。
  • 调试时可能难以跟踪请求经过的完整路径。
  • 如果处理者之间存在顺序依赖(必须先 A 后 B),需在构建链时保证,容易出配置错误。

与其他模式的关系

模式 区别与联系
装饰器模式 装饰器模式强调对功能进行增强,且请求一定会经过每一层装饰;责任链中请求可能中途终止。两者结构相似,但意图不同。
命令模式 命令模式将请求封装为对象并交给调用者执行;责任链中的处理者可以直接处理请求。可结合使用:命令在链中被传递。
观察者模式 观察者是对多个订阅者广播通知,所有观察者都能收到;责任链是按顺序尝试,一个处理者可能独占消费。
状态模式 状态模式通过改变对象内部状态来处理行为变化;责任链用不同对象表示处理行为。

总结

责任链模式通过将请求沿一条预定义的链进行传递,实现了请求发送者与最终接收者之间的解耦。它让你可以动态组合、重用和扩展处理逻辑,尤其适合多级别、多步骤的请求处理场景。学习该模式后,你将能更优雅地编写那些充满 if-else 和硬编码调用关系的模块,使系统更灵活、更易于维护。

一句话记忆:把一连串的“尝试处理”变成链上的独立节点,让客户端只知头、不知尾。