职责链模式:让请求沿着链条传递
职责链模式,说白了就是给请求找一条处理流水线。每个节点只关心自己能不能处理,处理不了就往后传。我在好几个项目中都用过这个模式,最典型的就是审批流程和日志系统。
模式动机与定义
先说说为什么需要这个模式。你想想看,如果一个请求需要多个对象来处理,而且处理顺序还不固定,怎么办?
最直接的做法就是写一堆if-else:
if (handlerA.canHandle(request)) {
handlerA.handle(request);
} else if (handlerB.canHandle(request)) {
handlerB.handle(request);
} else if (handlerC.canHandle(request)) {
handlerC.handle(request);
} else {
// 没人能处理
}
嗯,这样写的问题很明显。每次新增一个处理器,就得改这段代码。而且处理顺序写死了,想调整顺序还得改代码。我在一个旧项目里就见过这样的代码,一个if-else链长达30多层,维护起来简直噩梦。
职责链模式的定义:避免请求发送者与接收者耦合,让多个对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。
核心思想:每个处理器只关心自己能不能处理,处理不了就交给下一个。这样新增处理器只需要加一个节点,不需要改现有代码。
角色与结构
职责链模式涉及三个角色:
- Handler(抽象处理器):定义处理请求的接口,并持有下一个处理器的引用
- ConcreteHandler(具体处理器):实现处理逻辑,如果自己能处理就处理,否则传给下一个
- Client(客户端):构建职责链,并向链头发送请求
结构其实很简单,就是一个单向链表。每个节点都知道下一个节点是谁。
C++代码实现
先写抽象处理器基类。这里有个小技巧:用智能指针管理next,避免内存泄漏。
#include <iostream>
#include <memory>
#include <string>
// 抽象处理器
class Handler {
protected:
std::shared_ptr<Handler> nextHandler;
public:
virtual ~Handler() = default;
void setNext(std::shared_ptr<Handler> next) {
nextHandler = next;
}
virtual void handle(const std::string& request) {
if (nextHandler) {
nextHandler->handle(request);
} else {
std::cout << "没人能处理这个请求: " << request << std::endl;
}
}
};
具体处理器实现。每个处理器只关心自己能处理的请求类型:
// 具体处理器A - 处理"TypeA"请求
class ConcreteHandlerA : public Handler {
public:
void handle(const std::string& request) override {
if (request == "TypeA") {
std::cout << "ConcreteHandlerA 处理了请求: " << request << std::endl;
} else {
std::cout << "ConcreteHandlerA 无法处理,传给下一个" << std::endl;
Handler::handle(request); // 调用基类的handle,自动传递
}
}
};
// 具体处理器B - 处理"TypeB"请求
class ConcreteHandlerB : public Handler {
public:
void handle(const std::string& request) override {
if (request == "TypeB") {
std::cout << "ConcreteHandlerB 处理了请求: " << request << std::endl;
} else {
std::cout << "ConcreteHandlerB 无法处理,传给下一个" << std::endl;
Handler::handle(request);
}
}
};
// 具体处理器C - 处理"TypeC"请求
class ConcreteHandlerC : public Handler {
public:
void handle(const std::string& request) override {
if (request == "TypeC") {
std::cout << "ConcreteHandlerC 处理了请求: " << request << std::endl;
} else {
std::cout << "ConcreteHandlerC 无法处理,传给下一个" << std::endl;
Handler::handle(request);
}
}
};
客户端代码,构建职责链并发送请求:
int main() {
// 构建职责链
auto handlerA = std::make_shared<ConcreteHandlerA>();
auto handlerB = std::make_shared<ConcreteHandlerB>();
auto handlerC = std::make_shared<ConcreteHandlerC>();
handlerA->setNext(handlerB);
handlerB->setNext(handlerC);
// 发送各种请求
handlerA->handle("TypeA"); // 被A处理
handlerA->handle("TypeB"); // 被B处理
handlerA->handle("TypeC"); // 被C处理
handlerA->handle("TypeD"); // 没人能处理
return 0;
}
个人经验:我建议在基类Handler中提供一个默认的handle实现,这样子类只需要关注自己的处理逻辑,不需要重复写传递代码。这个习惯帮我减少了不少重复代码。
审批流程示例
职责链模式最经典的应用就是审批流程。我在一个OA系统里就用过这个模式,处理请假审批、报销审批等场景。
先定义审批请求的数据结构:
// 审批请求
struct ApprovalRequest {
std::string applicant; // 申请人
double amount; // 金额
int days; // 天数
std::string reason; // 原因
};
抽象审批处理器:
// 抽象审批处理器
class Approver {
protected:
std::shared_ptr<Approver> nextApprover;
std::string name;
public:
Approver(const std::string& n) : name(n) {}
virtual ~Approver() = default;
void setNext(std::shared_ptr<Approver> next) {
nextApprover = next;
}
virtual void approve(const ApprovalRequest& request) {
if (nextApprover) {
nextApprover->approve(request);
} else {
std::cout << "审批被拒绝: 金额" << request.amount
<< "超出所有审批权限" << std::endl;
}
}
};
具体审批角色:
// 项目经理 - 审批5000元以下
class ProjectManager : public Approver {
public:
ProjectManager() : Approver("项目经理") {}
void approve(const ApprovalRequest& request) override {
if (request.amount <= 5000) {
std::cout << name << " 审批通过: " << request.applicant
<< " 申请 " << request.amount << "元" << std::endl;
} else {
std::cout << name << " 权限不足,转交上级" << std::endl;
Approver::approve(request);
}
}
};
// 部门经理 - 审批20000元以下
class DepartmentManager : public Approver {
public:
DepartmentManager() : Approver("部门经理") {}
void approve(const ApprovalRequest& request) override {
if (request.amount <= 20000) {
std::cout << name << " 审批通过: " << request.applicant
<< " 申请 " << request.amount << "元" << std::endl;
} else {
std::cout << name << " 权限不足,转交上级" << std::endl;
Approver::approve(request);
}
}
};
// 总经理 - 审批100000元以下
class GeneralManager : public Approver {
public:
GeneralManager() : Approver("总经理") {}
void approve(const ApprovalRequest& request) override {
if (request.amount <= 100000) {
std::cout << name << " 审批通过: " << request.applicant
<< " 申请 " << request.amount << "元" << std::endl;
} else {
std::cout << name << " 权限不足,转交上级" << std::endl;
Approver::approve(request);
}
}
};
客户端使用:
int main() {
// 构建审批链
auto pm = std::make_shared<ProjectManager>();
auto dm = std::make_shared<DepartmentManager>();
auto gm = std::make_shared<GeneralManager>();
pm->setNext(dm);
dm->setNext(gm);
// 测试不同金额的审批
ApprovalRequest req1 = {"张三", 3000, 3, "团建活动"};
ApprovalRequest req2 = {"李四", 15000, 5, "设备采购"};
ApprovalRequest req3 = {"王五", 50000, 10, "项目外包"};
ApprovalRequest req4 = {"赵六", 200000, 30, "系统升级"};
pm->approve(req1);
std::cout << "---" << std::endl;
pm->approve(req2);
std::cout << "---" << std::endl;
pm->approve(req3);
std::cout << "---" << std::endl;
pm->approve(req4);
return 0;
}
输出结果:
项目经理 审批通过: 张三 申请 3000元
---
项目经理 权限不足,转交上级
部门经理 审批通过: 李四 申请 15000元
---
项目经理 权限不足,转交上级
部门经理 权限不足,转交上级
总经理 审批通过: 王五 申请 50000元
---
项目经理 权限不足,转交上级
部门经理 权限不足,转交上级
总经理 权限不足,转交上级
审批被拒绝: 金额200000超出所有审批权限
避坑指南:我曾经在一个项目中遇到职责链死循环的问题。原因是某个处理器处理完请求后,又调用了链头的handle方法,导致请求在链中循环传递。解决办法是:每个处理器要么处理请求并返回,要么只调用一次next->handle(),不要同时做两件事。
职责链模式的优缺点
| 优点 | 缺点 |
|---|---|
| 降低耦合度:发送者和接收者解耦 | 请求可能不被处理:如果链尾也没人处理 |
| 灵活性高:可以动态调整链的顺序 | 调试困难:请求在链中传递,难以追踪 |
| 符合开闭原则:新增处理器不需要改现有代码 | 性能问题:链太长会影响处理速度 |
| 每个处理器职责单一 | 可能形成循环链导致死循环 |
我的建议:使用职责链模式时,最好在基类中加一个最大链长限制,或者加一个请求ID来防止循环。另外,如果链中大部分请求都会被第一个处理器处理,那其实没必要用职责链,直接调用第一个处理器就行。职责链适合处理「不确定谁会处理」的场景。
职责链模式说白了就是「能者多劳,不能者让贤」。每个节点只做自己擅长的事,做不了就往后传。这种思想在很多场景都适用,比如日志级别过滤、事件冒泡、中间件架构等。你想想看,其实很多框架的拦截器机制,底层就是职责链模式。
在实际项目中,我建议把职责链的构建逻辑单独抽出来,不要散落在客户端代码里。可以写一个ChainBuilder或者ChainFactory,专门负责组装链。这样哪天想调整处理顺序,只需要改一个地方。
公众号:蓝海资料掘金营,微信deep3321