设计模式之责任链模式

发布于:2025-02-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

引言

在职场中,请假流程大家都再熟悉不过:申请 1 至 2 天的假期,只需直属主管审批即可;若要请假 3 至 5 天,就需部门负责人进行复核;而超过 5 天的假期申请,则必须由总经理最终定夺。要是遇到超长假期,甚至得上报至总裁或董事长那里。这种层层递进的审批机制,宛如一套设计精妙的权限传递系统:每位处理者只能在自己的职权范围内行使决策权,一旦请求超出了当前处理者的能力范围,便会依照既定的权力链条自动流转,直至抵达具备相应权限的决策节点。

这一场景,其实是软件设计模式里经典的责任链模式(Chain of Responsibility Pattern) 在现实世界中的生动呈现。责任链模式通过搭建动态的处理链路,让多个对象都有机会对请求进行处理。这种方式巧妙地实现了请求发起者与处理者之间的解耦,同时也赋予了系统处理逻辑出色的灵活性与扩展性。

概述

定义

责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行发送。当请求到达链中的某个处理者时,该处理者会决定是处理这个请求还是将其传递给链中的下一个处理者。这样就形成了一条责任链,请求在这条链上依次传递,直到有一个处理者能够处理它为止。

结构

  • 抽象处理者(Handler):定义了一个处理请求的抽象方法,并持有一个指向下一个处理者的引用。它为具体处理者提供了统一的接口,使得所有处理者都具有相同的处理请求的方法签名。
  • 具体处理者(Concrete Handler):实现了抽象处理者定义的处理请求的方法。在具体处理者中,会根据自身的逻辑来判断是否能够处理请求,如果能够处理,则进行处理;如果不能处理,则将请求传递给下一个处理者。
  • 客户端(Client):负责创建责任链,并向链的起始端发送请求。客户端不需要知道具体是哪个处理者最终处理了请求,只需要将请求发送到责任链上即可。

在这里插入图片描述

工作原理

  • 首先,客户端创建责任链,将各个具体处理者按照一定的顺序连接起来,形成一条链。
  • 然后,客户端向链的起始端的处理者发送请求。
  • 当请求到达某个具体处理者时,该处理者会检查自己是否能够处理这个请求。如果可以处理,就执行相应的处理逻辑,处理完请求后,请求的传递过程就结束了。如果不能处理,它就会将请求传递给链中的下一个处理者。
  • 这个过程会一直持续,直到请求被某个处理者处理或者到达链的末端都没有被处理。如果到达链的末端仍未被处理,可以根据具体情况进行相应的处理,比如抛出异常或者返回一个默认结果。

示例

我们以引言中的例子编写示例代码

类图

在这里插入图片描述

C++实现

#include <iostream>
#include <string>
#include <memory>

// 请假请求对象
class LeaveRequest {
public:
    int days;
    std::string name;

    LeaveRequest(int d, const std::string& n) : days(d), name(n) {}
};

// 抽象处理者
class Handler {
protected:
    std::unique_ptr<Handler> successor;  // 下一级处理者
    const std::string title; // 职位名称

public:
    explicit Handler(const std::string& t) : title(t) {}
    virtual ~Handler() = default;

    // 设置下一级处理者
    void setSuccessor(std::unique_ptr<Handler> next) {
        successor = std::move(next);
    }

    // 处理请求的抽象方法
    virtual void processRequest(const LeaveRequest& req) = 0;

protected:
    // 默认传递行为
    void delegateRequest(const LeaveRequest& req) {
        if (successor) {
            successor->processRequest(req);
        } else {
            std::cout << "⚠️ " << req.name << "的" << req.days
                      << "天假期申请需要更高权限审批" << std::endl;
        }
    }
};

// 具体处理者:直属主管
class Supervisor : public Handler {
public:
    Supervisor() : Handler("直属主管") {}

    void processRequest(const LeaveRequest& req) override {
        if (req.days <= 2) {
            std::cout << "✅ [" << title << "] 批准" << req.name
                      << "的" << req.days << "天假期" << std::endl;
        } else {
            std::cout << "➡️ [" << title << "] 将" << req.name
                      << "的申请递交给上级" << std::endl;
            delegateRequest(req);
        }
    }
};

// 具体处理者:部门负责人
class DepartmentManager : public Handler {
public:
    DepartmentManager() : Handler("部门负责人") {}

    void processRequest(const LeaveRequest& req) override {
        if (req.days <= 5) {
            std::cout << "✅ 【" << title << "】批准" << req.name
                      << "的" << req.days << "天假期" << std::endl;
        } else {
            std::cout << "➡️ 【" << title << "】将" << req.name
                      << "的申请递交给上级" << std::endl;
            delegateRequest(req);
        }
    }
};

// 具体处理者:总经理
class GeneralManager : public Handler {
public:
    GeneralManager() : Handler("总经理") {}

    void processRequest(const LeaveRequest& req) override {
        if (req.days <= 15) {
            std::cout << "✅ 【★" << title << "★】批准" << req.name
                      << "的" << req.days << "天假期" << std::endl;
        } else {
            std::cout << "➡️ 【★" << title << "★】将" << req.name
                      << "的申请递交给上级" << std::endl;
            delegateRequest(req);
        }
    }
};

// 具体处理者:董事长
class Chairman : public Handler {
public:
    Chairman() : Handler("董事长") {}

    void processRequest(const LeaveRequest& req) override {
        std::cout << "✅ 【最高决议】" << title << "批准" << req.name
                  << "的" << req.days << "天长假" << std::endl;
    }
};

int main() {
    // 使用智能指针创建责任链
    auto bob = std::make_unique<Chairman>();
    auto lucy = std::make_unique<GeneralManager>();
    auto emma = std::make_unique<DepartmentManager>();
    auto john = std::make_unique<Supervisor>();

    // 构建责任链(逆序设置)
    lucy->setSuccessor(std::move(bob));     // 总经理持有董事长
    emma->setSuccessor(std::move(lucy));    // 部门经理持有总经理
    john->setSuccessor(std::move(emma));    // 主管持有部门经理

    // 测试用例
    LeaveRequest requests[] = {
            {1, "张三"},
            {3, "李四"},
            {7, "王五"},
            {20, "赵六"},
            {30, "陈七"}
    };

    for (const auto& req : requests) {
        std::cout << "\n====== 处理" << req.name
                  << "的" << req.days << "天假期申请 ======" << std::endl;
        john->processRequest(req);
    }

    return 0;
}

运行结果如下:

在这里插入图片描述

Java实现

import java.util.Objects;

/**
 * 请假请求实体类(Immutable Object)
 */
final class LeaveRequest {
    private final int days;
    private final String name;

    /**
     * 构造请假请求
     * @param days 请假天数(必须大于0)
     * @param name 申请人姓名(非空)
     */
    public LeaveRequest(int days, String name) {
        if (days <= 0) throw new IllegalArgumentException("请假天数必须为正数");
        this.days = days;
        this.name = Objects.requireNonNull(name, "申请人姓名不能为空");
    }

    public int getDays() {
        return days;
    }

    public String getName() {
        return name;
    }
}

/**
 * 抽象处理者(Handler)
 */
abstract class Handler {
    protected Handler nextHandler;
    protected final String title;

    /**
     * 构造处理者
     * @param title 处理者职位名称
     */
    protected Handler(String title) {
        this.title = title;
    }

    /**
     * 设置下一级处理者(构建责任链)
     * @param nextHandler 下一级处理者实例
     * @return 当前处理者(支持链式调用)
     */
    public Handler setNext(Handler nextHandler) {
        this.nextHandler = nextHandler;
        return this;
    }

    /**
     * 处理请求的模板方法
     * @param request 请假请求
     */
    public final void handleRequest(LeaveRequest request) {
        if (canHandle(request)) {
            approve(request);
        } else {
            delegate(request);
        }
    }

    /**
     * 判断当前处理者能否处理请求
     * @param request 请假请求
     * @return 是否具有处理权限
     */
    protected abstract boolean canHandle(LeaveRequest request);

    /**
     * 审批通过的具体操作
     * @param request 请假请求
     */
    protected void approve(LeaveRequest request) {
        System.out.printf("✅ [%s] 批准 %s 的 %d 天假期%n", 
                        title, request.getName(), request.getDays());
    }

    /**
     * 转交请求给下一级处理者
     * @param request 请假请求
     */
    protected void delegate(LeaveRequest request) {
        System.out.printf("➡️ [%s] 将 %s 的申请递交给上级%n", 
                        title, request.getName());
        if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.printf("⚠️ %s 的 %d 天假期申请需要更高权限审批%n",
                            request.getName(), request.getDays());
        }
    }
}

/**
 * 直属主管(Concrete Handler)
 * 处理规则:可批准1-2天的假期
 */
class Supervisor extends Handler {
    public Supervisor() {
        super("直属主管");
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= 2;
    }
}

/**
 * 部门经理(Concrete Handler)
 * 处理规则:可批准3-5天的假期
 */
class DepartmentManager extends Handler {
    public DepartmentManager() {
        super("部门经理");
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= 5;
    }
}

/**
 * 总经理(Concrete Handler)
 * 处理规则:可批准6-15天的假期
 */
class GeneralManager extends Handler {
    public GeneralManager() {
        super("总经理");
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= 15;
    }

    @Override
    protected void approve(LeaveRequest request) {
        System.out.printf("✅ 【★%s★】批准 %s 的 %d 天长假%n", 
                        title, request.getName(), request.getDays());
    }
}

/**
 * 董事长(Concrete Handler)
 * 处理规则:可批准任意天数的假期
 */
class Chairman extends Handler {
    public Chairman() {
        super("董事长");
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return true; // 最终处理节点
    }

    @Override
    protected void approve(LeaveRequest request) {
        System.out.printf("✅ 【最高决议】%s 批准 %s 的 %d 天超长假期%n", 
                        title, request.getName(), request.getDays());
    }
}

/**
 * 客户端使用示例
 */
public class ChainOfResponsibilityDemo {
    public static void main(String[] args) {
        // 构建责任链
        Handler chain = new Supervisor()
            .setNext(new DepartmentManager())
            .setNext(new GeneralManager())
            .setNext(new Chairman());

        // 创建测试请求
        LeaveRequest[] requests = {
            new LeaveRequest(1, "张三"),
            new LeaveRequest(3, "李四"),
            new LeaveRequest(7, "王五"),
            new LeaveRequest(20, "赵六"),
            new LeaveRequest(30, "陈七")
        };

        // 处理所有请求
        for (LeaveRequest request : requests) {
            System.out.printf("%n====== 处理 %s 的 %d 天假期申请 ======%n",
                            request.getName(), request.getDays());
            chain.handleRequest(request);
        }
    }
}

代码说明:

  1. 责任链结构
    • Supervisor(直属主管):处理1-2天假期
    • DepartmentManager(部门负责人):处理3-5天假期
    • GeneralManager(总经理):处理6-15天假期
    • Chairman(董事长):处理15天以上超长假期
  2. 运行特征
    • 每个处理者只处理权限范围内的请求
    • 超出权限时自动传递到下一级
    • 董事长作为最终处理节点
    • 使用统一接口processRequest处理请求

责任链模式遵循的设计原则

单一职责原则(Single Responsibility Principle)

  • 含义:一个类应该只有一个引起它变化的原因,即每个类只负责一项职责。
  • 在责任链模式中的体现:在责任链模式里,每个具体处理者类只负责处理特定范围内的请求,只关注自身的处理逻辑。例如在公司请假审批的责任链中,直属主管只负责审批 1 - 2 天的请假申请,部门负责人只处理 3 - 5 天的请假申请,总经理处理 6 - 15 天的请假申请等。每个处理者的职责单一明确,当某个处理者的处理规则发生变化时,只需要修改该处理者类,不会影响到其他处理者。

开闭原则(Open - Closed Principle)

  • 含义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,在不修改现有代码的基础上,能够扩展新的功能。
  • 在责任链模式中的体现:当需要添加新的处理者时,只需要创建一个新的具体处理者类,实现抽象处理者接口,并将其添加到责任链中即可,而不需要修改现有的处理者类和客户端代码。例如,在一个日志处理的责任链中,如果要增加一种新的日志过滤规则,只需要创建一个新的日志过滤器类并插入到合适的位置,原有的日志处理逻辑不受影响。

里氏替换原则(Liskov Substitution Principle)

  • 含义:子类可以替换其父类并且出现在父类能够出现的任何地方,而不会影响系统的正确性。
  • 在责任链模式中的体现:具体处理者类继承自抽象处理者类,它们可以完全替换抽象处理者类在责任链中的位置。客户端代码只与抽象处理者类进行交互,不需要关心具体是哪个具体处理者类在处理请求。例如,在一个图形绘制的责任链中,不同类型的图形绘制处理者(如圆形绘制处理者、矩形绘制处理者等)都继承自抽象的图形绘制处理者,它们可以互相替换,不影响责任链的正常运行。

依赖倒置原则(Dependency Inversion Principle)

  • 含义:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
  • 在责任链模式中的体现:客户端代码(高层模块)不直接依赖具体的处理者类(低层模块),而是依赖抽象处理者类。具体处理者类也依赖于抽象处理者类来定义其行为。这样,当具体处理者类发生变化时,不会影响到客户端代码。例如,在一个订单处理的责任链中,客户端只需要与抽象的订单处理者接口进行交互,而具体的订单处理者(如折扣处理者、库存检查处理者等)都实现该接口,降低了模块之间的耦合度。

迪米特法则(Law of Demeter)

  • 含义:一个对象应该对其他对象有最少的了解,即一个类应该尽量减少与其他类的交互。
  • 在责任链模式中的体现:每个具体处理者只与它的下一个处理者进行交互,不需要了解责任链中其他处理者的具体实现。处理者只负责处理自己能够处理的请求,如果不能处理则将请求传递给下一个处理者,避免了处理者之间的过度依赖和复杂的交互关系。例如,在一个电商系统的优惠券处理责任链中,每个优惠券处理者只知道如何处理自己负责的优惠券类型以及将不处理的请求传递给下一个处理者,不需要了解其他优惠券处理者的细节。

责任链模式的优缺点

优点

1. 解耦请求发送者和接收者

  • 请求的发送者只需要将请求发送到责任链上,而不需要知道具体是哪个处理者会处理这个请求。处理者之间的关系由责任链来维护,它们只需要关注自身的处理逻辑和如何将请求传递给下一个处理者。
  • 例如,在一个公司的请假审批系统中,员工(请求发送者)只需要提交请假申请,不需要知道是组长、部门经理还是总经理最终会批准这个申请,降低了员工与各级审批者之间的耦合度。

2. 增强灵活性和可扩展性

  • 可以很方便地增加或删除处理者,或者改变处理者的顺序,而不需要对客户端代码进行大量修改。新的处理者只需要实现抽象处理者接口,并按照规则添加到责任链中即可。
  • 比如,在一个电商系统的订单处理流程中,如果需要增加一个新的处理环节,如对特定商品订单进行额外的审核,只需要创建一个新的处理者类并插入到合适的位置,不会影响原有的订单处理逻辑。

3. 简化对象之间的交互

  • 避免了多个对象之间相互引用、相互依赖的复杂关系。每个处理者只需要关心自己是否能够处理请求以及如何将请求传递给下一个处理者,使得代码结构更加清晰,易于理解和维护。
  • 以一个游戏中的事件处理系统为例,鼠标点击事件可能会经过多个组件的处理,使用责任链模式可以让每个组件只专注于自己的处理逻辑,而不需要了解其他组件的具体情况。

4. 符合开闭原则

  • 对扩展开放,对修改关闭。当需要添加新的处理者时,只需要创建新的具体处理者类并加入到责任链中,而不需要修改现有的处理者类和客户端代码。
  • 例如,在一个日志处理系统中,如果需要增加一种新的日志过滤规则,只需要创建一个新的日志过滤器类并添加到责任链中,不会影响原有的日志处理流程。

缺点

1. 请求可能得不到处理

  • 如果责任链没有正确配置,或者链中没有合适的处理者能够处理请求,那么请求可能会一直传递到链的末端,最终得不到处理。这可能会导致一些潜在的问题,需要在代码中进行额外的处理。
  • 比如,在一个文件处理的责任链中,如果所有的处理者都无法处理某种类型的文件,而又没有相应的错误处理机制,那么这个文件的处理请求就会被忽略。

2. 调试难度增加

  • 由于请求在责任链中传递,当出现问题时,很难确定是哪个处理者出现了问题。需要逐步跟踪请求的传递过程,排查每个处理者的处理逻辑,这增加了调试的难度和时间成本。
  • 例如,在一个复杂的业务流程中,一个请求经过多个处理者的处理后出现了错误,需要逐个检查每个处理者的输入和输出,才能找到问题所在。

3. 性能问题

  • 如果责任链过长,请求的传递和处理会涉及多个对象的调用,这会增加系统的开销,降低性能。特别是在处理大量请求时,性能问题可能会更加明显。
  • 比如,在一个实时性要求较高的系统中,一个请求在经过多个处理者的传递和处理后,可能会导致响应时间过长,影响用户体验。

4. 代码复杂度提升

  • 责任链模式需要定义抽象处理者、具体处理者等多个类,并且需要正确配置责任链,这会增加代码的复杂度。对于一些简单的业务场景,使用责任链模式可能会显得过于繁琐。
  • 例如,在一个只需要简单处理几个固定类型请求的系统中,使用责任链模式会引入不必要的类和代码,增加开发和维护的难度。

注意事项

责任链的配置与管理

  • 正确构建责任链
    • 要确保责任链中各个处理者的顺序正确,符合业务逻辑。例如在请假审批流程中,应该按照从基层领导到高层领导的顺序依次构建责任链,若顺序错误,可能导致低权限的领导处理了本应由高权限领导处理的请求,或者请求无法得到正确处理。
    • 责任链的起点和终点要明确,避免出现循环引用或者无终止的情况。如果处理者之间形成了循环引用,请求会在链中不断循环,导致系统资源耗尽。
  • 动态调整责任链
    • 当业务需求发生变化时,可能需要动态地添加、删除或修改责任链中的处理者。这就要求在设计时考虑如何方便地进行这些操作,同时要保证责任链的完整性和正确性。例如,在一个电商订单处理系统中,促销活动期间可能需要临时增加一个优惠券处理环节到责任链中。

请求处理与传递

  • 请求的完整性
    • 在责任链中传递的请求对象应该包含足够的信息,以便每个处理者能够根据这些信息做出正确的处理决策。如果请求信息不完整,可能会导致处理者无法正确处理请求或者做出错误的判断。例如,在一个文件处理责任链中,请求对象应包含文件的类型、大小等关键信息。
  • 避免请求丢失
    • 要确保每个处理者在无法处理请求时,能够正确地将请求传递给下一个处理者。如果某个处理者在不能处理请求时没有正确传递,请求可能会丢失,导致业务流程中断。可以在抽象处理者中提供默认的传递方法,确保请求能正常流转。
  • 处理结果的反馈
    • 考虑如何将处理结果反馈给客户端。有些情况下,客户端可能需要知道请求是否被成功处理以及处理的具体结果。可以在处理者处理完请求后,通过返回值或者回调函数的方式将结果反馈给客户端。

性能与调试

  • 性能开销
    • 责任链过长会增加请求处理的时间和系统开销,因为请求需要依次经过多个处理者。在设计责任链时,要权衡责任链的长度,避免不必要的处理者加入链中。可以通过性能测试来评估责任链的性能,并根据结果进行优化。
  • 调试难度
    • 由于请求在责任链中传递,调试时很难确定是哪个处理者出现了问题。可以在处理者中添加日志记录,记录请求的接收、处理和传递情况,方便在出现问题时进行排查。同时,也可以使用调试工具来跟踪请求的传递过程。

异常处理

  • 处理者内部异常
    • 每个处理者在处理请求时可能会抛出异常,需要在处理者内部进行适当的异常处理。例如,在一个数据库操作的责任链中,某个处理者在执行数据库查询时可能会出现数据库连接异常,处理者应该捕获并处理这些异常,避免异常影响到整个责任链的执行。
  • 全局异常处理
    • 除了处理者内部的异常处理,还需要考虑全局的异常处理机制。当整个责任链都无法处理请求或者出现严重错误时,要有相应的处理方式,如返回错误信息给客户端或者进行系统级别的日志记录。

应用场景

审批流程类场景

  • 公司请假与报销审批:在公司中,员工请假或申请报销时,通常需要经过多个层级的审批。例如,请假天数较少时可能只需直属领导批准,请假天数较多则需要部门经理、总经理等依次审批。每个审批者就是一个处理者,构成了一条责任链。请求(请假或报销申请)从员工发起,沿着责任链依次传递,直到有合适的审批者处理该请求。
  • 项目立项审批:大型项目的立项需要经过多个部门和层级的评估与审批,如业务部门、财务部门、技术部门等。每个部门都有自己的审批职责和权限,项目立项申请在各部门之间依次流转,形成一个责任链。

事件处理类场景

  • 图形界面事件处理:在图形界面应用程序中,鼠标点击、键盘输入等事件可能需要经过多个组件或层级的处理。例如,当用户点击一个按钮时,事件首先由按钮组件处理,如果按钮组件不处理,可能会传递给父容器组件,再到窗口组件等,形成一条事件处理的责任链。
  • 游戏中的事件响应:在游戏开发中,各种游戏事件(如玩家攻击、角色受伤等)可能需要经过不同的系统进行处理。例如,玩家攻击事件可能先由战斗系统判断攻击是否有效,再由伤害计算系统计算伤害值,最后由角色状态系统更新角色的生命值等,这些系统构成了一个事件处理的责任链。

过滤器与拦截器场景

  • Web 应用请求过滤:在 Web 应用中,一个 HTTP 请求可能需要经过多个过滤器的处理,如身份验证过滤器、权限检查过滤器、数据加密过滤器等。每个过滤器都有自己的职责,按照顺序构成责任链对请求进行处理。只有通过前面过滤器的请求才能继续传递到下一个过滤器,直到最终到达业务处理逻辑。
  • 日志处理过滤器:在日志系统中,日志信息可能需要经过多个过滤器的处理,如日志级别过滤、日志内容过滤等。不同的过滤器可以根据不同的规则对日志信息进行筛选和处理,确保只有符合条件的日志信息才会被记录或输出。

错误处理与重试机制场景

  • 分布式系统中的错误处理:在分布式系统中,当某个服务调用失败时,可能需要进行一系列的错误处理和重试操作。例如,首先尝试进行本地重试,若本地重试失败,则尝试切换到备用服务,若备用服务也不可用,则通知管理员等。这些错误处理步骤可以构成一个责任链,依次对错误进行处理。
  • 网络请求重试机制:在进行网络请求时,可能会因为网络波动等原因导致请求失败。可以构建一个责任链,其中每个处理者负责不同类型的重试策略,如指数退避重试、固定次数重试等。当请求失败时,请求会依次经过这些处理者,尝试不同的重试方式,直到请求成功或达到最大重试次数。

数据处理与转换场景

  • 数据清洗与转换流程:在数据处理过程中,原始数据可能需要经过多个步骤的清洗和转换。例如,先去除数据中的空值,再进行数据格式转换,最后进行数据加密等。每个步骤可以看作一个处理者,构成责任链对数据进行逐步处理。
  • 文档处理流程:在文档处理中,文档可能需要经过多个处理环节,如格式检查、内容审核、排版调整等。每个处理环节都有自己的处理逻辑,形成一个责任链对文档进行处理。