责任链模式:请求传递,处理者链
责任链模式,说白了就是给请求找一串处理者。每个处理者看完请求,要么自己处理掉,要么扔给下一个。就像公司里的审批流程——你提交一个报销单,组长先看,金额太大就转给经理,经理觉得搞不定再转给总监。
我刚开始接触这个模式时,觉得它不就是个链表嘛。后来在日志系统里用了一次,才发现它的设计精髓在于「解耦」。发送者根本不需要知道谁会处理,它只管把请求丢进链里就行。
模式的核心思想
责任链模式的核心就三点:
- 请求发送者:只负责发出请求,不关心谁处理
- 处理者接口:定义统一的处理方法和设置下一个处理者的方法
- 具体处理者:决定自己能处理什么,不能处理就传给下一个
你想想看,这跟现实中的「踢皮球」其实是一个道理。只不过在软件里,我们管这叫「职责分离」。
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 流程图:责任链模式结构
避坑指南
我曾经踩过的一个坑:责任链的组装顺序搞反了。DEBUG 级别的日志应该先经过 ConsoleLogger,再经过 FileLogger。如果顺序反了,FileLogger 先处理,它发现级别不够就扔给下一个,结果 ConsoleLogger 收到了,但 FileLogger 自己反而没记录。嗯,这种 bug 特别隐蔽,日志一多根本看不出来。
另外要注意的是,责任链不能形成环。我曾经见过一个同事把处理者的 setNext 指向了自己,结果程序直接栈溢出。虽然这种低级错误不常见,但设计时最好加个保护机制,比如限制链的最大长度。
什么时候用责任链?
- 你有多个处理者,但不确定谁会处理请求
- 你想动态地添加或移除处理者
- 处理者的顺序会影响结果
- 你想避免发送者和接收者之间的强耦合
我个人觉得,责任链模式最大的价值在于「可组合性」。你可以在运行时动态调整链的结构,而不需要修改任何业务代码。这在插件系统、中间件架构里特别有用。
小技巧:如果处理者很多,可以考虑用 std::vector 来管理链,而不是手动 setNext。这样遍历起来更方便,也更容易做调试。
好了,责任链模式就聊到这里。记住它的核心:请求传递,处理者链。下次你写日志系统、过滤器或者审批流程时,不妨试试这个模式。