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_即可。
责任链模式的核心流程图
避坑指南
我曾经踩过的一个坑:在责任链的末尾忘记加一个“兜底处理者”。结果某个请求一路传到底,没人处理,程序静默失败,排查了半天才发现。
解决方案:在链的末尾加一个默认处理者,要么处理请求,要么打日志警告。
另外要注意:责任链不宜过长。如果链上有十几个处理者,每次请求都要遍历一遍,性能会受影响。我个人的经验是,超过5个节点就要考虑是否该拆分了。
责任链 vs 其他模式
| 模式 | 核心区别 |
|---|---|
| 责任链 | 请求沿着链传递,每个节点决定是否处理 |
| 装饰器 | 每个节点都处理,并增强功能 |
| 命令模式 | 请求被封装成对象,支持撤销、队列等操作 |
说白了,责任链的核心是“传递”,装饰器的核心是“增强”,命令模式的核心是“封装”。面试时如果能把这个区别讲清楚,面试官会高看你一眼。
总结
责任链模式在处理多种类型请求时特别有用。它让代码更灵活,每个处理者只关心自己的事,新增处理类型时不需要改现有代码——开闭原则的完美体现。
嗯,面试时如果能手写一个日志责任链,再讲讲纯责任链和不纯责任链的区别,基本就稳了。