
C++职责链模式设计理念:让请求在对象链上优雅旅行
大家好,今天我想和大家深入聊聊C++中的职责链模式(Chain of Responsibility)。这个模式在我处理那些“不知道找谁处理,或者需要多个对象依次尝试”的业务场景时,简直是个神器。它就像公司里的审批流程,一个请求(比如报销单)从基层员工开始,如果无权处理,就传递给经理,经理不行再给总监,直到有人能处理为止。这种解耦发送者和接收者的思想,让代码的灵活性大大提升。
一、为什么我们需要职责链模式?
记得之前我维护过一个旧系统的消息处理模块。最初的代码是一大坨复杂的if-else if或者switch-case语句,用来根据消息类型决定由哪个处理器处理。每当要新增一种消息类型,我就得战战兢兢地去修改那个庞大的中心判断函数,生怕影响了其他逻辑。这种代码耦合度高,难以维护和扩展。
职责链模式正是为了解决这个问题而生。它的核心设计理念是:将多个处理对象(处理器)连成一条链,请求沿着这条链传递,直到有一个对象处理它为止。 发送者无需关心最终由谁处理,从而实现了请求发送者和接收者的解耦。每个处理器只关注自己是否能够处理,不能处理则传递给下一个,这非常符合“单一职责”原则。
二、职责链模式的结构与角色
在C++中实现职责链,通常涉及以下几个关键角色:
- Handler(抽象处理者):定义处理请求的接口,并持有下一个处理者的引用(后继者)。这是整个模式的骨架。
- ConcreteHandler(具体处理者):实现抽象处理者的接口,判断自己是否能处理请求。能则处理,不能则转发给后继者。
- Client(客户端):创建职责链,并向链的头部提交请求。
下面,我们通过一个最经典的例子——费用报销审批流程,来把理论落地。假设报销金额小于1000元由经理批,小于5000元由总监批,5000元以上需要CEO审批。
三、实战:构建一个报销审批链
首先,我们定义抽象处理者类 Approver。这里我选择在基类中实现设置后继者的逻辑,这是纯虚函数,具体处理者必须实现如何处理请求。
#include
#include
#include
// 抽象处理者:审批人
class Approver {
public:
virtual ~Approver() = default;
// 设置后继者
void setSuccessor(std::shared_ptr successor) {
successor_ = successor;
}
// 处理请求的纯虚函数
virtual void processRequest(int amount) = 0;
protected:
std::shared_ptr successor_; // 指向下一个处理者的指针
};
接下来,我们实现具体的处理者:经理(Manager)、总监(Director)和CEO。这里有个小技巧:我通常会在具体处理者的processRequest函数中,将无法处理的请求明确地传递给后继者,这样逻辑更清晰。
// 具体处理者:经理
class Manager : public Approver {
public:
void processRequest(int amount) override {
if (amount <= 1000) {
std::cout << "经理批准了 " << amount << " 元的报销单。" << std::endl;
} else if (successor_ != nullptr) {
std::cout << "经理无权限,转交给上级。" <processRequest(amount); // 传递给后继者
} else {
std::cout << "金额 " << amount << " 元过高,无人能处理。" << std::endl;
}
}
};
// 具体处理者:总监
class Director : public Approver {
public:
void processRequest(int amount) override {
if (amount <= 5000) {
std::cout << "总监批准了 " << amount << " 元的报销单。" << std::endl;
} else if (successor_ != nullptr) {
std::cout << "总监无权限,转交给上级。" <processRequest(amount);
} else {
std::cout << "金额 " << amount << " 元过高,无人能处理。" << std::endl;
}
}
};
// 具体处理者:CEO
class CEO : public Approver {
public:
void processRequest(int amount) override {
if (amount <= 10000) {
std::cout << "CEO批准了 " << amount << " 元的报销单。" << std::endl;
} else {
std::cout << "金额 " << amount << " 元过高,即使CEO也无法批准。" << std::endl;
}
// CEO是链的末端,没有后继者,或者也可以选择不处理。
}
};
现在,让我们在客户端(main函数)中组装这条链并测试。这里我使用std::shared_ptr来管理对象生命周期,避免内存泄漏的坑。
int main() {
// 1. 创建具体的处理者
auto manager = std::make_shared();
auto director = std::make_shared();
auto ceo = std::make_shared();
// 2. 构建职责链:经理 -> 总监 -> CEO
manager->setSuccessor(director);
director->setSuccessor(ceo);
// CEO是链尾,可以不设置后继者
std::cout << "=== 报销审批流程测试 ===" <processRequest(800); // 经理处理
std::cout << "--------" <processRequest(3500); // 经理转总监处理
std::cout << "--------" <processRequest(8500); // 经理转总监转CEO处理
std::cout << "--------" <processRequest(12000); // 传递到CEO也无法处理
return 0;
}
运行这个程序,你会看到请求如何根据金额大小,在“经理-总监-CEO”这条链上流动,每个处理器各司其职。这种设计的优雅之处在于,如果未来要增加一个“副总裁”的审批环节,我只需要新建一个VicePresident类,并在客户端调整链的组装顺序即可,完全不需要修改已有的处理器类代码。
四、深入思考与踩坑提示
通过上面的例子,相信你已经掌握了基础。但在实际项目中,还有一些细节需要注意:
1. 链的终止条件: 必须确保链的末尾有合理的处理(或终止)逻辑,否则请求可能“石沉大海”,导致程序行为异常。就像上面的CEO类,它明确处理了它能处理的,并拒绝了过高的金额。
2. 性能考量: 如果链非常长,且大部分请求都需要传递到链尾才能处理,那么性能可能会成为问题。在设计时,可以考虑将最可能被处理的处理器放在链的前面。
3. 请求可能未被处理: 这是职责链模式的固有特点。客户端需要意识到,提交的请求有可能没有任何处理器接手。根据业务需求,你可能需要在链的末端添加一个“默认处理器”或“异常抛出器”。
4. 动态修改链: 职责链可以在运行时动态改变,这既是优点也是复杂度来源。确保在修改链结构(比如增加、删除或重排处理器)时,处理好对象间的引用关系,避免出现循环引用或空指针访问。
五、总结:何时该用职责链模式?
回顾我的使用经验,职责链模式特别适用于以下场景:
- 多级处理或审批流程(如我们刚实现的例子)。
- 事件冒泡机制(如GUI中的事件处理)。
- 中间件管道(如Web服务器中的请求过滤器链,每个过滤器检查请求并决定是否传递给下一个)。
- 当你想避免在发送者和多个接收者之间建立硬编码的、复杂的关联时。
它的精髓在于通过“链”这个结构,将处理逻辑分散到多个独立的类中,降低了耦合度,增强了系统的可扩展性和可维护性。下次当你面对一堆复杂的条件分支,并且感觉它们各自代表独立的业务逻辑时,不妨考虑一下职责链模式,它很可能就是让代码变得更优雅的那把钥匙。
希望这篇结合实战和踩坑经验的分享能帮到你。在C++的世界里,理解设计模式的思想远比死记硬背类图重要。多思考,多实践,你一定能用得更好。

评论(0)