77、STL与责任链:责任链模式、请求处理、动态链构建
责任链模式,说白了就是让一堆处理对象排成一条队。请求从队头进来,挨个问:「你能处理我吗?」谁行谁上,不行就往后传。我刚开始接触这个模式时觉得挺简单,后来在项目中才发现,真正用好它,关键在于「链」的动态构建能力。
你想想看,如果链是写死的,那跟if-else有啥区别?STL容器给了我们动态管理节点的能力,这才是责任链的精髓所在。
责任链的核心思想
责任链模式解决的是「请求发送者」和「请求接收者」之间的解耦问题。每个处理者只关心自己能不能处理,不关心前面是谁、后面是谁。
我习惯把责任链比作流水线上的工位。每个工位只做一件事,做不了就传给下一个。这样新增一个工位,完全不影响其他工位。
- 处理者接口:定义统一的处理方法和设置后继者的接口
- 具体处理者:实现自己的处理逻辑,决定是否传递
- 链的管理:动态构建、调整处理顺序
STL如何助力责任链
传统责任链实现中,每个节点都持有下一个节点的指针。这种静态链表的缺点很明显:增删节点需要修改代码,运行时无法调整顺序。
STL的std::vector、std::list、std::unique_ptr等容器和智能指针,让我们可以动态管理处理者列表。我个人最喜欢用std::vector<std::unique_ptr<Handler>>,既保证了所有权清晰,又支持随机访问。
代码实战:日志级别过滤器
来看一个实际例子。假设我们要实现一个日志系统,不同级别的日志由不同的处理器负责。DEBUG日志写入调试文件,INFO日志打印到控制台,ERROR日志发送邮件告警。
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 日志级别枚举
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR
};
// 处理者基类
class LogHandler {
public:
virtual ~LogHandler() = default;
virtual void handle(const std::string& message, LogLevel level) = 0;
};
// 具体处理者:DEBUG级别
class DebugHandler : public LogHandler {
public:
void handle(const std::string& message, LogLevel level) override {
if (level == LogLevel::DEBUG) {
std::cout << "[DEBUG] " << message << std::endl;
}
// 不传递,DEBUG只处理自己的级别
}
};
// 具体处理者:INFO级别
class InfoHandler : public LogHandler {
public:
void handle(const std::string& message, LogLevel level) override {
if (level == LogLevel::INFO) {
std::cout << "[INFO] " << message << std::endl;
}
}
};
// 具体处理者:ERROR级别
class ErrorHandler : public LogHandler {
public:
void handle(const std::string& message, LogLevel level) override {
if (level == LogLevel::ERROR) {
std::cerr << "[ERROR] " << message << " - 已发送告警邮件" << std::endl;
}
}
};
// 责任链管理器
class LoggerChain {
private:
std::vector<std::unique_ptr<LogHandler>> handlers;
public:
// 动态添加处理者
template<typename T, typename... Args>
void addHandler(Args&&... args) {
handlers.emplace_back(std::make_unique<T>(std::forward<Args>(args)...));
}
// 处理日志
void log(const std::string& message, LogLevel level) {
for (auto& handler : handlers) {
handler->handle(message, level);
}
}
};
int main() {
LoggerChain chain;
chain.addHandler<DebugHandler>();
chain.addHandler<InfoHandler>();
chain.addHandler<ErrorHandler>();
chain.log("这是一条调试信息", LogLevel::DEBUG);
chain.log("用户登录成功", LogLevel::INFO);
chain.log("数据库连接失败", LogLevel::ERROR);
return 0;
}
这段代码里,每个处理器只处理自己关心的级别。注意看,我没有让处理器之间互相持有指针,而是用vector统一管理。这样做的好处是:
- 添加新处理器只需调用
addHandler,不改现有代码 - 可以运行时决定处理顺序
- 内存管理由unique_ptr自动搞定
动态链构建的进阶技巧
上面的例子是「广播式」责任链——每个处理器都收到请求。但经典责任链是「传递式」的:一个处理器处理完,可以选择是否传给下一个。
我曾经在项目中遇到一个需求:用户提交的表单需要经过多个校验器,每个校验器通过后才传给下一个。一旦某个校验失败,立即返回错误,不再继续。
这种场景下,我让每个处理器返回一个布尔值,表示「是否继续传递」:
class Validator {
public:
virtual ~Validator() = default;
virtual bool validate(const std::string& input) = 0;
};
class ChainValidator {
std::vector<std::unique_ptr<Validator>> validators;
public:
void add(std::unique_ptr<Validator> v) {
validators.push_back(std::move(v));
}
bool validate(const std::string& input) {
for (auto& v : validators) {
if (!v->validate(input)) {
return false; // 一旦失败,立即中断
}
}
return true;
}
};
责任链的适用场景
| 场景 | 说明 | 推荐实现 |
|---|---|---|
| 日志过滤 | 不同级别日志不同处理 | 广播式,每个处理器独立 |
| 输入校验 | 多个校验规则依次执行 | 传递式,短路求值 |
| 权限检查 | 逐级检查用户权限 | 传递式,找到匹配即停止 |
| 事件处理 | UI事件冒泡 | 传递式,可中途拦截 |
SVG:责任链模式结构图
避坑指南
我踩过不少责任链的坑,挑几个典型的说说:
- 链的循环问题:我曾经在动态构建时不小心让A指向B、B指向C、C又指向A,结果死循环。用vector管理就不会有这个问题。
- 性能开销:如果链很长(比如上百个处理器),每次请求都遍历一遍,性能会下降。我建议用
std::partition或std::sort对处理器按优先级排序,高频处理器放前面。 - 线程安全:多线程环境下,动态修改链容器需要加锁。我习惯用
std::shared_mutex做读写分离。
std::priority_queue来管理。不过要注意,优先队列不支持遍历,需要自己封装。
责任链模式配上STL,就像给流水线装上了可编程控制器。你可以在运行时决定谁来处理、处理顺序、是否中断。这种灵活性,在复杂业务系统中特别有用。
嗯,最后说一句:不要为了用模式而用模式。如果只有两三个处理分支,简单的if-else反而更清晰。责任链的优势在于「动态」和「扩展」,用对地方才是好代码。