Java 设计模式心法之第20篇 - 责任链 (Chain of Responsibility) - 解耦请求处理,实现灵活传递

在软件系统中,一个请求的发出者(客户端)往往不清楚(也不应该清楚)这个请求最终应该由哪个对象来处理,或者一个请求可能需要经过一系列不同对象的检查或处理才能完成。如果让请求发送者直接持有所有潜在处理者的引用,并自行判断将请求交给谁,会导致发送者与处理者之间产生紧密的耦合,并且处理流程的变更会非常困难。本文将带你深入理解行为型模式中的“流水线工人”——责任链模式。我们将揭示它如何将多个处理者对象连接成一条,让请求在这条链上传递,直到遇到能够处理它的对象为止(或者链结束),从而实现请求发送者与接收者的解耦,并允许动态地组合和修改处理者链,极大地增强了请求处理的灵活性和可扩展性。Servlet Filter 和 Struts2 的 Interceptor 都体现了责任链模式的思想。


一、问题的提出:当“请求找人”遭遇“职责不清”与“流程僵化”

想象一下公司内部的费用报销审批流程

  • 员工 (Client): 提交报销申请(请求)。
  • 审批者 (Handlers):
    • 直接主管 (Direct Manager): 可能审批小额(如 500 元以下)报销。
    • 部门经理 (Department Manager): 可能审批中等额度(如 5000 元以下)。
    • 财务总监 (CFO): 可能审批更高额度。
    • CEO: 审批超大额度或特殊申请。

员工提交申请时,他不应该需要知道具体的审批层级和金额限制,也不应该需要自己判断“我这个金额该找谁批?”。他只需要把申请单提交上去即可。

如果我们让员工(或提交系统)直接根据金额 if-else 判断去找对应的审批人:

// 糟糕的设计:客户端直接决定处理者
class ExpenseReport { double amount; /* ... */ }
class Employee {
    DirectManager manager;
    DepartmentManager deptManager;
    CFO cfo;
    // ...

    void submitReport(ExpenseReport report) {
        System.out.println("提交报销申请,金额:" + report.amount);
        if (report.amount < 500) {
            manager.approve(report);
        } else if (report.amount < 5000) {
            deptManager.approve(report);
        } else if (report.amount < 20000) {
            cfo.approve(report);
        } else {
            // 可能需要 CEO 审批?或者其他逻辑?
            System.out.println("金额过大,需要更高级别审批...");
            // 逻辑复杂且难以扩展
        }
    }
}

问题显而易见:

  1. 发送者与接收者紧密耦合 (Tight Coupling): Employee 类必须知道所有可能的审批者 (DirectManager, DepartmentManager, CFO 等) 及其审批逻辑(金额范围)。
  2. 违反开闭原则 (OCP): 如果审批流程改变(比如调整金额限制、增加新的审批级别如“项目经理”),必须修改 Employee 类的 submitReport 方法。
  3. 职责不清 (Unclear Responsibility): 决定请求由谁处理的逻辑,本不应该是请求发送者的职责。
  4. 流程僵化 (Inflexible Flow): 审批流程被硬编码在客户端,难以动态调整(比如临时增加一个“财务复核”环节)。

我们需要一种机制,能够:

  • 解耦请求的发送者和接收者。 发送者只需发出请求,无需关心谁来处理。
  • 让多个对象都有机会处理请求。
  • 处理者可以动态地组合和排序。
  • 请求能够沿着一条预设的路径自动传递。

二、链式传递的智慧:责任链模式的核心定义与意图

责任链模式 (Chain of Responsibility Pattern) 提供了一种优雅的方式来解决这个问题。它避免请求的发送者接收者耦合在一起,让多个对象都有机会处理这个请求。该模式将这些接收对象(处理者)连接成一条,并且让请求沿着这条链传递,直到链上的某个处理者决定处理它为止。

GoF 的经典意图描述是:“使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。”

其核心思想在于:

  1. 定义处理者接口/抽象类 (Handler Interface/Abstract Class): 定义一个处理请求的接口,通常包含一个 handleRequest(Request request) 方法。同时,为了形成链条,还需要一个方法来设置下一个处理者 (setNextHandler(Handler next)),并持有一个指向下一个处理者的引用。
  2. 具体处理者实现 (Concrete Handler Implementation): 每个具体处理者类实现了 Handler 接口。在 handleRequest 方法中,它首先判断自己是否有能力或责任处理当前请求
    • 如果能处理: 则处理该请求,并且可以选择是否将请求继续传递给链上的下一个处理者(取决于具体业务需求,是处理后终止,还是处理后继续传递)。
    • 如果不能处理(或不完全处理): 则将请求原封不动地(或者处理一部分后)传递给下一个处理者(通过调用 nextHandler.handleRequest(request))。如果自己是链上的最后一个处理者且无法处理,则请求可能得不到处理(或者有默认处理)。
  3. 客户端设置链条并发起请求 (Client Sets up Chain and Initiates Request): 客户端负责创建处理者对象,并将它们按照特定的顺序连接成一条链(通过调用 setNextHandler)。然后,客户端只需要将请求发送给链的第一个处理者即可,无需关心请求最终由谁处理或如何传递。

核心角色:

  • Handler (处理者接口/抽象类): 定义了处理请求的接口 (handleRequest),以及设置和获取下一个处理者的接口 (setNext, getNext)。维护对下一个处理者的引用。
  • ConcreteHandler (具体处理者): 实现 Handler 接口。判断自己是否能处理请求,如果能则处理,否则(或处理后)将请求转发给下一个处理者。
  • Client (客户端): 创建处理者链,并将请求发送给链的起始节点。

关键:将处理者链接起来,请求沿链传递,每个处理者决定是处理请求、传递请求,还是两者都做。

三、灵活流转的场景:责任链模式的适用之地

责任链模式非常适用于以下情况:

  • 有多个对象可以处理同一个请求,但具体由哪个对象处理在运行时动态确定: 你不希望在发送者代码里硬编码处理者的选择逻辑。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交请求: 发送者与接收者解耦。
  • 可处理请求的对象集合应被动态指定: 链的结构(处理者的顺序、包含哪些处理者)可以在运行时改变。
  • 请求的处理流程需要灵活可变: 可以通过调整链的构成来改变处理流程。
  • Java Web 开发中的 Filter (过滤器) 链: javax.servlet.Filter 接口及其实现类构成了一个典型的责任链。每个 Filter 都有机会处理请求和响应,并通过调用 chain.doFilter(request, response) 将请求传递给链上的下一个 Filter 或最终的目标 Servlet。开发者可以通过配置 web.xml 或使用注解来动态地添加、移除或调整 Filter 的顺序。
  • 日志框架中的 Logger 层级: 日志请求会从具体的 Logger 开始,沿着父级 Logger 链向上传递,直到找到一个配置了处理级别(如 Appender)的 Logger,或者到达根 Logger。
  • GUI 事件冒泡 (Event Bubbling): 在某些 GUI 框架中,一个事件(如点击)会先在触发事件的组件上尝试处理,如果该组件不处理,事件会“冒泡”到其父容器,再到父容器的父容器,形成一条处理链。
  • 异常处理机制 (try-catch-finally): 虽然不是严格的模式实现,但 catch 块的匹配过程也类似责任链,异常会沿着 catch 块链传递,直到找到第一个能处理该异常类型的 catch 块。

四、链式处理的实现:责任链模式的 Java 实践

我们用之前的费用报销审批例子来实现责任链模式。

1. 定义处理者接口/抽象类 (Handler):
(这里用抽象类,可以包含设置下一个处理者的公共逻辑)

/**
 * 处理者抽象类
 */
abstract class Approver {
    protected Approver nextApprover; // 指向链中的下一个处理者
    protected String name; // 处理者姓名/职位

    public Approver(String name) { this.name = name; }

    // 设置下一个处理者
    public void setNextApprover(Approver next) {
        this.nextApprover = next;
        System.out.println(this.name + " 的下一个审批者是: " + (next != null ? next.name : "无"));
    }

    // 处理请求的抽象方法,由子类实现
    public abstract void processRequest(ExpenseReport report);
}

// 报销单类
class ExpenseReport {
    public final double amount;
    public final String purpose;
    public ExpenseReport(double amount, String purpose) {
        this.amount = amount;
        this.purpose = purpose;
    }
}

2. 创建具体处理者类 (ConcreteHandler):

/**
 * 具体处理者A:直接主管
 */
class DirectManager extends Approver {
    private final double APPROVAL_LIMIT = 500.0;

    public DirectManager(String name) { super(name); }

    @Override
    public void processRequest(ExpenseReport report) {
        System.out.println("[" + this.name + "] 开始审批报销 '" + report.purpose + "', 金额: " + report.amount);
        if (report.amount <= APPROVAL_LIMIT) {
            System.out.println("直接主管 [" + this.name + "] 批准了报销申请。");
            // 处理完成,可以选择不再向下传递 (取决于业务)
        } else {
            System.out.println("金额超过直接主管权限 (" + APPROVAL_LIMIT + "),转交下一级审批。");
            if (nextApprover != null) {
                nextApprover.processRequest(report); // 传递给下一个处理者
            } else {
                System.out.println("没有更高级别的审批者,申请无法处理。");
            }
        }
    }
}

/**
 * 具体处理者B:部门经理
 */
class DepartmentManager extends Approver {
    private final double APPROVAL_LIMIT = 5000.0;

    public DepartmentManager(String name) { super(name); }

    @Override
    public void processRequest(ExpenseReport report) {
        System.out.println("[" + this.name + "] 开始审批报销 '" + report.purpose + "', 金额: " + report.amount);
        if (report.amount <= APPROVAL_LIMIT) {
            System.out.println("部门经理 [" + this.name + "] 批准了报销申请。");
        } else {
            System.out.println("金额超过部门经理权限 (" + APPROVAL_LIMIT + "),转交下一级审批。");
            if (nextApprover != null) {
                nextApprover.processRequest(report);
            } else {
                System.out.println("没有更高级别的审批者,申请无法处理。");
            }
        }
    }
}

/**
 * 具体处理者C:财务总监 (CFO)
 */
class CFO extends Approver {
     private final double APPROVAL_LIMIT = 20000.0; // 假设 CFO 最高审批 2 万

    public CFO(String name) { super(name); }

    @Override
    public void processRequest(ExpenseReport report) {
        System.out.println("[" + this.name + "] 开始审批报销 '" + report.purpose + "', 金额: " + report.amount);
        if (report.amount <= APPROVAL_LIMIT) {
            System.out.println("CFO [" + this.name + "] 批准了报销申请。");
        } else {
            System.out.println("金额超过 CFO 权限 (" + APPROVAL_LIMIT + "),需要更高级别审批。");
            if (nextApprover != null) {
                nextApprover.processRequest(report);
            } else {
                 System.out.println("最终审批权限不足,申请无法处理。");
            }
        }
    }
}
// 如果需要 CEO 审批,只需再创建一个 CEO 类继承 Approver 即可

3. 客户端设置链条并发起请求 (Client):

public class ChainOfResponsibilityClient {
    public static void main(String[] args) {
        // 1. 创建处理者对象
        Approver manager = new DirectManager("王经理");
        Approver deptManager = new DepartmentManager("李总监");
        Approver cfo = new CFO("张 CFO");
        // Approver ceo = new CEO("马老板"); // 可以轻松扩展

        // 2. 设置责任链 (处理顺序:主管 -> 部门经理 -> CFO)
        System.out.println("=== 设置审批链 ===");
        manager.setNextApprover(deptManager);
        deptManager.setNextApprover(cfo);
        // cfo.setNextApprover(ceo); // 如果有 CEO

        System.out.println("\n=== 提交报销申请 ===");

        // 创建几个不同金额的报销单
        ExpenseReport report1 = new ExpenseReport(300, "购买办公用品");
        ExpenseReport report2 = new ExpenseReport(3000, "团队建设费用");
        ExpenseReport report3 = new ExpenseReport(15000, "采购服务器");
        ExpenseReport report4 = new ExpenseReport(50000, "重大项目投资"); // 假设超过 CFO 权限

        // 3. 将请求发送给链的第一个处理者 (manager)
        System.out.println("\n--- 处理报销单 1 ---");
        manager.processRequest(report1); // 应由 manager 直接批准

        System.out.println("\n--- 处理报销单 2 ---");
        manager.processRequest(report2); // 应由 deptManager 批准

        System.out.println("\n--- 处理报销单 3 ---");
        manager.processRequest(report3); // 应由 cfo 批准

        System.out.println("\n--- 处理报销单 4 ---");
        manager.processRequest(report4); // 最终无人批准 (或如果链上有 CEO 则由 CEO 处理)

        // 客户端完全不知道哪个审批者最终处理了请求,只负责构建链和发起请求。
        // 调整审批流程(如增加或移除审批人、改变顺序)只需修改链的构建代码,无需修改客户端或处理者类。
    }
}

代码解读:

  • Approver 定义了处理者的基本接口和链结构。
  • DirectManager, DepartmentManager, CFO 是具体处理者,各自实现了 processRequest,包含了自己的处理逻辑(判断金额)和向下传递的逻辑(调用 nextApprover.processRequest)。
  • 客户端负责创建处理者实例,并使用 setNextApprover 将它们链接起来形成审批链。
  • 客户端将报销请求 (ExpenseReport) 提交给链的第一个节点 (manager)。
  • 请求会自动沿着链传递,直到找到一个能处理它的审批者,或者到达链的末端。

五、模式的价值:责任链带来的解耦与灵活

责任链模式的核心价值在于其提供的解耦灵活性

  1. 降低耦合度 (Reduced Coupling):
    • 发送者与接收者解耦: 请求的发送者(Client)完全不需要知道是哪个对象处理了它的请求。
    • 处理者之间解耦: 每个处理者只需要知道它的下一个处理者是谁(通过 nextApprover 引用),而不需要知道链上的其他处理者或链的整体结构。
  2. 增强了给对象指派职责的灵活性 (Flexibility in Assigning Responsibilities): 你可以通过改变链内的成员或者调动它们的次序,来动态地改变处理一个请求的职责分配。
  3. 提高代码的可扩展性和可维护性 (Improved Extensibility & Maintainability):
    • 符合开闭原则 (OCP): 增加新的处理者非常容易,只需创建一个新的 ConcreteHandler 类,并将其插入到链中的合适位置即可,无需修改现有代码。
    • 符合单一职责原则 (SRP): 每个 ConcreteHandler 只负责处理它能处理的那部分请求逻辑。
  4. 请求处理流程清晰(逻辑上): 虽然实现分散在各个处理者中,但请求沿链传递的逻辑是清晰的。

六、权衡与考量:责任链模式的注意事项

使用责任链模式也需要注意:

  1. 请求不保证被处理 (Request Not Guaranteed to Be Handled): 如果请求到达链的末端仍未被处理,它可能会“石沉大海”。需要根据业务需求考虑是否需要一个默认的处理者,或者在链末端进行特殊处理(如抛异常、记录日志)。
  2. 可能影响性能 (Potential Performance Impact): 请求需要沿着链逐一传递,如果链条过长,或者某些处理者的判断逻辑耗时,可能会影响请求的处理速度。
  3. 链的构建与维护 (Chain Construction & Maintenance): 需要有机制(客户端代码、配置文件、工厂等)来正确地构建和维护这条责任链。如果链的结构复杂或经常变动,维护成本可能会增加。
  4. 调试困难 (Debugging Complexity): 追踪一个请求在链上的完整处理过程可能比较困难,需要逐个检查处理者的逻辑。

七、心法归纳:链接职责,传递请求

责任链模式的核心“心法”在于**“链接”与“传递”**:

  1. 链接职责 (Chain Handlers): 将能够处理同一类请求的多个对象(处理者)通过引用(通常是 next 指针)连接成一条链条
  2. 传递请求 (Pass the Request): 请求从链的首端进入,沿着链逐一传递给每个处理者。每个处理者检查自己是否有责任或能力处理该请求,如果处理,则可以选择终止传递继续传递;如果不处理,则必须将其传递给链上的下一个处理者。

掌握责任链模式,意味着你拥有了:

  • 解耦请求发送与处理的有效武器: 让系统更灵活、更易于扩展。
  • 构建可动态配置处理流程的能力: 通过调整链的结构来改变行为。
  • 实现类似过滤器 (Filter) 或拦截器 (Interceptor) 机制的基础。
  • 一种优雅处理“多个对象可能处理同一请求”场景的方案。

当你需要让多个对象都有机会处理一个请求,又不希望发送者与接收者紧密耦合,或者希望处理流程能够灵活变化时,责任链模式就是你设计工具箱中那条能够“各司其职、有序传递”的“流水线”。它鼓励我们将职责分散,通过链式结构实现协作,是构建许多健壮、可扩展系统的常用模式。


下一章预告: 《Java 设计模式心法:命令 (Command) - 将请求封装成对象,实现操作解耦与扩展》。如果我们需要将一个操作请求本身(比如“开灯”、“关门”、“保存文件”)封装成一个对象,以便可以参数化客户端、对请求排队、记录日志或实现撤销/重做功能,该怎么办?命令模式将为我们展示这种将“请求对象化”的强大威力。敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码觉客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值