42. 设计模式面试题7:实现一个责任链模式,处理多种类型的请求

责任链模式,说白了就是给请求找一串“处理人”。

每个处理人看看这活儿自己能不能干,能干就干,不能干就往后传。我当年刚接触这个模式时,觉得它跟链表差不多,后来在项目中用了几次才发现——这玩意儿在解耦方面是真的香。

什么是责任链模式?

它属于行为型设计模式。核心思想是:将多个处理对象连成一条链,沿着这条链传递请求,直到有对象处理它为止

你想想看,这就像公司里的审批流程。普通员工请假3天,组长批了就行;超过3天,组长签完意见再往上递。每个节点只关心自己权限范围内的事,超出范围就交给下一个。

核心角色:
  • Handler(抽象处理者):定义处理请求的接口,持有下一个处理者的引用
  • ConcreteHandler(具体处理者):实现自己的处理逻辑,决定是否继续传递
  • Client(客户端):构造链,并向链头发送请求

为什么需要责任链?

我在项目中遇到过这样一个场景:系统需要处理多种类型的日志——错误日志要发邮件,警告日志要写文件,调试日志只打印控制台。如果不用责任链,代码会写成这样:

void processLog(Log log) {
    if (log.type == ERROR) {
        sendEmail(log);
    } else if (log.type == WARNING) {
        writeFile(log);
    } else if (log.type == DEBUG) {
        consolePrint(log);
    }
}

这种写法有什么问题?

  • 每加一种日志类型,就要改这个函数
  • 处理逻辑耦合在一起,没法单独测试
  • 条件分支越来越多,代码越来越臭

责任链模式就是来解决这个问题的。

代码实现:处理多种类型的请求

我们来实现一个日志处理系统。先定义抽象处理者:

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

// 日志请求
struct LogRequest {
    enum Level { DEBUG, WARNING, ERROR };
    Level level;
    std::string message;
    
    LogRequest(Level l, const std::string& msg) 
        : level(l), message(msg) {}
};

// 抽象处理者
class LogHandler {
protected:
    std::shared_ptr<LogHandler> nextHandler_;
    
public:
    virtual ~LogHandler() = default;
    
    void setNext(std::shared_ptr<LogHandler> next) {
        nextHandler_ = next;
    }
    
    virtual void handle(const LogRequest& request) {
        if (nextHandler_) {
            nextHandler_->handle(request);
        }
    }
};

然后实现三个具体处理者:

// 调试日志处理者
class DebugHandler : public LogHandler {
public:
    void handle(const LogRequest& request) override {
        if (request.level == LogRequest::DEBUG) {
            std::cout << "[DEBUG] " << request.message << std::endl;
        } else {
            LogHandler::handle(request);  // 传给下一个
        }
    }
};

// 警告日志处理者
class WarningHandler : public LogHandler {
public:
    void handle(const LogRequest& request) override {
        if (request.level == LogRequest::WARNING) {
            std::cout << "[WARNING] " << request.message << std::endl;
            // 警告日志处理完后,可以继续传递
            LogHandler::handle(request);
        } else {
            LogHandler::handle(request);
        }
    }
};

// 错误日志处理者
class ErrorHandler : public LogHandler {
public:
    void handle(const LogRequest& request) override {
        if (request.level == LogRequest::ERROR) {
            std::cout << "[ERROR] " << request.message << std::endl;
            // 错误日志是终点,不再传递
        } else {
            LogHandler::handle(request);
        }
    }
};

客户端组装链并发送请求:

int main() {
    // 构建责任链
    auto debugHandler = std::make_shared<DebugHandler>();
    auto warningHandler = std::make_shared<WarningHandler>();
    auto errorHandler = std::make_shared<ErrorHandler>();
    
    debugHandler->setNext(warningHandler);
    warningHandler->setNext(errorHandler);
    
    // 发送请求
    LogRequest req1(LogRequest::DEBUG, "变量x未初始化");
    LogRequest req2(LogRequest::WARNING, "内存使用率超过80%");
    LogRequest req3(LogRequest::ERROR, "数据库连接失败");
    
    debugHandler->handle(req1);
    debugHandler->handle(req2);
    debugHandler->handle(req3);
    
    return 0;
}

输出结果:

[DEBUG] 变量x未初始化
[WARNING] 内存使用率超过80%
[ERROR] 数据库连接失败

责任链的两种变体

类型 特点 适用场景
纯责任链 一个请求只能被一个处理者处理 审批流程、权限校验
不纯责任链 一个请求可以被多个处理者处理 日志处理、过滤器链

上面的日志例子属于不纯责任链——WarningHandler处理完后,还把请求传给了ErrorHandler。如果你想要纯责任链,只需要在某个处理者处理完后不再调用nextHandler_即可。

责任链模式的核心流程图

责任链模式处理流程 客户端 Handler1: DebugHandler 能处理? 处理请求 不能 Handler2: WarningHandler
个人经验:我建议在实现责任链时,给每个处理者加一个“优先级”字段。这样可以在运行时动态调整链的顺序,而不需要改代码。我曾经在一个网关项目中这么做过,效果非常好。

避坑指南

我曾经踩过的一个坑:在责任链的末尾忘记加一个“兜底处理者”。结果某个请求一路传到底,没人处理,程序静默失败,排查了半天才发现。

解决方案:在链的末尾加一个默认处理者,要么处理请求,要么打日志警告。

另外要注意:责任链不宜过长。如果链上有十几个处理者,每次请求都要遍历一遍,性能会受影响。我个人的经验是,超过5个节点就要考虑是否该拆分了。

责任链 vs 其他模式

模式 核心区别
责任链 请求沿着链传递,每个节点决定是否处理
装饰器 每个节点都处理,并增强功能
命令模式 请求被封装成对象,支持撤销、队列等操作

说白了,责任链的核心是“传递”,装饰器的核心是“增强”,命令模式的核心是“封装”。面试时如果能把这个区别讲清楚,面试官会高看你一眼。

总结

责任链模式在处理多种类型请求时特别有用。它让代码更灵活,每个处理者只关心自己的事,新增处理类型时不需要改现有代码——开闭原则的完美体现。

嗯,面试时如果能手写一个日志责任链,再讲讲纯责任链和不纯责任链的区别,基本就稳了。


专注资料整理