责任链模式:请求传递,处理者链

责任链模式,说白了就是给请求找一串处理者。每个处理者看完请求,要么自己处理掉,要么扔给下一个。就像公司里的审批流程——你提交一个报销单,组长先看,金额太大就转给经理,经理觉得搞不定再转给总监。

我刚开始接触这个模式时,觉得它不就是个链表嘛。后来在日志系统里用了一次,才发现它的设计精髓在于「解耦」。发送者根本不需要知道谁会处理,它只管把请求丢进链里就行。

模式的核心思想

责任链模式的核心就三点:

  • 请求发送者:只负责发出请求,不关心谁处理
  • 处理者接口:定义统一的处理方法和设置下一个处理者的方法
  • 具体处理者:决定自己能处理什么,不能处理就传给下一个

你想想看,这跟现实中的「踢皮球」其实是一个道理。只不过在软件里,我们管这叫「职责分离」。

C++中的日志级别处理

日志系统是责任链模式最经典的例子。我记得有一次接手一个老项目,日志代码散落在各个模块里,有的用 printf,有的用 cout,还有的直接写文件。想统一日志级别?那叫一个痛苦。

后来我用责任链重构了日志系统。先定义一个抽象基类:

class Logger {
public:
    Logger(LogLevel level) : m_level(level), m_next(nullptr) {}
    
    void setNext(Logger* next) {
        m_next = next;
    }
    
    void log(LogLevel level, const std::string& message) {
        if (m_level <= level) {
            write(message);
        }
        if (m_next) {
            m_next->log(level, message);
        }
    }
    
    virtual ~Logger() = default;
    
protected:
    virtual void write(const std::string& message) = 0;
    
private:
    LogLevel m_level;
    Logger* m_next;
};

然后实现几个具体的处理者:

class ConsoleLogger : public Logger {
public:
    ConsoleLogger(LogLevel level) : Logger(level) {}
    
protected:
    void write(const std::string& message) override {
        std::cout << "[Console] " << message << std::endl;
    }
};

class FileLogger : public Logger {
public:
    FileLogger(LogLevel level, const std::string& filename) 
        : Logger(level), m_file(filename, std::ios::app) {}
    
protected:
    void write(const std::string& message) override {
        m_file << "[File] " << message << std::endl;
    }
    
private:
    std::ofstream m_file;
};

class EmailLogger : public Logger {
public:
    EmailLogger(LogLevel level) : Logger(level) {}
    
protected:
    void write(const std::string& message) override {
        // 实际项目中这里会调用邮件发送接口
        std::cout << "[Email] " << message << std::endl;
    }
};

组装成链:

int main() {
    Logger* console = new ConsoleLogger(LogLevel::DEBUG);
    Logger* file = new FileLogger(LogLevel::INFO, "app.log");
    Logger* email = new EmailLogger(LogLevel::ERROR);
    
    console->setNext(file);
    file->setNext(email);
    
    console->log(LogLevel::DEBUG, "这是一条调试信息");
    console->log(LogLevel::INFO, "这是一条普通信息");
    console->log(LogLevel::ERROR, "系统发生严重错误");
    
    delete console;
    delete file;
    delete email;
    return 0;
}

关键点:每个处理者只处理自己级别及以上的日志,然后继续传给下一个。这样 DEBUG 级别的日志只会出现在控制台,ERROR 级别的日志会同时出现在控制台、文件和邮件里。

责任链的两种变体

我习惯把责任链分成两种:

类型 特点 适用场景
纯责任链 一个请求只被一个处理者处理 审批流程、路由分发
不纯责任链 请求可以被多个处理者处理 日志系统、过滤器链

日志系统属于「不纯责任链」。每个处理者处理完自己的部分,还会继续往下传。而审批流程属于「纯责任链」——组长批了,就不用再找经理了。

SVG 流程图:责任链模式结构

责任链模式结构图 客户端 处理者1 (ConsoleLogger) 级别: DEBUG 处理者2 (FileLogger) 级别: INFO 处理者3 (EmailLogger) 级别: ERROR

避坑指南

我曾经踩过的一个坑:责任链的组装顺序搞反了。DEBUG 级别的日志应该先经过 ConsoleLogger,再经过 FileLogger。如果顺序反了,FileLogger 先处理,它发现级别不够就扔给下一个,结果 ConsoleLogger 收到了,但 FileLogger 自己反而没记录。嗯,这种 bug 特别隐蔽,日志一多根本看不出来。

另外要注意的是,责任链不能形成环。我曾经见过一个同事把处理者的 setNext 指向了自己,结果程序直接栈溢出。虽然这种低级错误不常见,但设计时最好加个保护机制,比如限制链的最大长度。

什么时候用责任链?

  • 你有多个处理者,但不确定谁会处理请求
  • 你想动态地添加或移除处理者
  • 处理者的顺序会影响结果
  • 你想避免发送者和接收者之间的强耦合

我个人觉得,责任链模式最大的价值在于「可组合性」。你可以在运行时动态调整链的结构,而不需要修改任何业务代码。这在插件系统、中间件架构里特别有用。

小技巧:如果处理者很多,可以考虑用 std::vector 来管理链,而不是手动 setNext。这样遍历起来更方便,也更容易做调试。

好了,责任链模式就聊到这里。记住它的核心:请求传递,处理者链。下次你写日志系统、过滤器或者审批流程时,不妨试试这个模式。


专注资料整理