# 责任链模式 从这一篇开始,我们开始学习第三类:**行为型模式**。 行为型模式关心的是"对象之间怎么通信,怎么分配职责",告诉你怎么让东西动起来、配合起来。 今天要讲的责任链模式,就是行为型模式中最直观的一个。 想想这些场景:一个请假申请,组长能批3天以内,经理能批7天以内,总监能批15天以内;一个报销单,500块以内主管签字就行,5000块以上要财务经理审批;一个异常日志,INFO级别记一下就行,ERROR级别要发短信告警。 这些场景有个共同特点:处理逻辑是分层的,不同层级有不同的处理权限。请求来了,需要沿着链条传递,直到有人能处理为止。 如果不用设计模式,你会怎么写?一堆if-else嵌套?switch-case满天飞?每个处理者都硬编码所有规则? 责任链模式就是为这种场景而生的。 ## 一、痛点场景:请假审批的噩梦 来看一个具体的例子。公司请假审批系统: - • 3天以内,组长批准 - • 3到7天,经理批准 - • 7天以上,总监批准 不用设计模式,代码大概长这样: ```cpp void handleRequest(int days) { if (days <= 3) { std::cout << "组长批准了" << days << "天假期" << std::endl; } else if (days <= 7) { std::cout << "经理批准了" << days << "天假期" << std::endl; } else if (days <= 15) { std::cout << "总监批准了" << days << "天假期" << std::endl; } else { std::cout << "请假时间太长,拒绝" << std::endl; } } ``` 看起来没问题,对吧?但如果你要加新规则呢? - • 15天以上需要HR审批 - • 连续请假超过5天要扣除年假 - • 试用期员工请假需要额外审批 每加一条规则,你就要改这个函数。改着改着,这个函数就变成了几百行的怪物,里面塞满了各种条件判断。更糟糕的是,这些规则可能分散在不同的地方:有的在这里,有的在另一个函数,有的在另一个文件。你想弄清楚"一个7天假期到底要走哪些流程",得把整个代码库翻一遍。 这就是典型的"职责混乱":所有处理逻辑都堆在一起,耦合度极高,改一个地方可能影响其他地方。 ![图片](https://mmbiz.qpic.cn/mmbiz_png/rZIMmoVTpsYAg6PQH8lveqNyWkv6ystlLBBSdq9nqCREEDeNHaWKGC7kqCaXC6bLcK4mvFqiabpzMg3TJFmDBDzO30IEJ27ia1wzIvlDq2Evg/640?wx_fmt=png&from=appmsg&watermark=1&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=0) 责任链模式的优势 ## 二、核心思想与本质 责任链模式的思路很清晰: > **把处理者串成一条链,请求沿着链传递。每个处理者决定自己能不能处理,能处理就处理,不能处理就传给下一个。** 用UML类图来表达: ![图片](https://mmbiz.qpic.cn/sz_mmbiz_png/rZIMmoVTpsYiauxxAgxdwOrpAFO66CIlLoFfoaaxdOl0Em9Qia4iak2lmicTYZ1zhImUVOnRUfNNwUm2hOztJCLhP81ichySlEB6Iz5WHg8uqgkk/640?wx_fmt=png&from=appmsg&watermark=1&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=1) 责任链模式UML类图 核心角色有三个: **Handler(抽象处理者)**:定义处理请求的接口,持有下一个处理者的引用。这是链条的基础结构。 **ConcreteHandler(具体处理者)**:实现具体的处理逻辑。如果能处理就处理,不能处理就传给下一个。 **Client(客户端)**:创建链条,把请求发给链条的第一个节点。 责任链的本质是**解耦请求和处理**。调用者不需要知道谁来处理这个请求,只需要把请求扔给链条。链条上的每个处理者也不需要知道链条的全貌,只需要关心"我能处理吗?不能就传给下一个"。 这种设计带来两个好处: **第一,职责清晰。** 每个处理者只关心自己能不能处理,不关心其他处理者的逻辑。组长只管3天以内的,经理只管3到7天的,互不干扰。 **第二,扩展容易。** 要加新规则?加一个新的处理者,插到链条里就行。不用改现有代码,符合开闭原则。 ## 三、C++完整实战代码 我们用责任链模式来实现一个请假审批流程 先看看这条责任链是什么样的: ![图片](https://mmbiz.qpic.cn/sz_mmbiz_png/rZIMmoVTpsYmFdHKYyEeubktRgACyzuIXycNickRH2p2Frl97vmTzYiaqCHgmPUqctL9fUGIpEJsicF0oPP8qgAFicAeSfVXfGgEhraVeHupaH0/640?wx_fmt=png&from=appmsg&watermark=1&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=2) 再用代码实现: ```cpp #include #include #include // ========== 1. 请求 ========== struct LeaveRequest { std::string name; int days; }; // ========== 2. 抽象处理者 ========== class Handler { public: virtual ~Handler() = default; virtual void handleRequest(const LeaveRequest& request) = 0; void setNext(std::shared_ptr next) { next_ = next; } protected: std::shared_ptr next_; }; // ========== 3. 具体处理者 ========== class GroupLeader : public Handler { public: void handleRequest(const LeaveRequest& request) override { if (request.days <= 3) { std::cout << "组长批准了 " << request.name << " 的" << request.days << "天假期" << std::endl; } else if (next_) { std::cout << "组长处理不了,转给上级..." << std::endl; next_->handleRequest(request); } } }; class Manager : public Handler { public: void handleRequest(const LeaveRequest& request) override { if (request.days <= 7) { std::cout << "经理批准了 " << request.name << " 的" << request.days << "天假期" << std::endl; } else if (next_) { std::cout << "经理处理不了,转给上级..." << std::endl; next_->handleRequest(request); } } }; class Director : public Handler { public: void handleRequest(const LeaveRequest& request) override { if (request.days <= 15) { std::cout << "总监批准了 " << request.name << " 的" << request.days << "天假期" << std::endl; } else { std::cout << "总监也处理不了,请假时间太长,拒绝" << std::endl; } } }; // ========== 4. 客户端 ========== int main() { // 创建处理者 auto groupLeader = std::make_shared(); auto manager = std::make_shared(); auto director = std::make_shared(); // 串成链条:组长 -> 经理 -> 总监 groupLeader->setNext(manager); manager->setNext(director); std::cout << "=== 测试1:请假2天 ===" << std::endl; groupLeader->handleRequest({"张三", 2}); std::cout << std::endl; std::cout << "=== 测试2:请假5天 ===" << std::endl; groupLeader->handleRequest({"李四", 5}); std::cout << std::endl; std::cout << "=== 测试3:请假10天 ===" << std::endl; groupLeader->handleRequest({"王五", 10}); std::cout << std::endl; std::cout << "=== 测试4:请假20天 ===" << std::endl; groupLeader->handleRequest({"赵六", 20}); return 0; } ``` 运行结果: ```bash === 测试1:请假2天 === 组长批准了 张三 的2天假期 === 测试2:请假5天 === 组长处理不了,转给上级... 经理批准了 李四 的5天假期 === 测试3:请假10天 === 组长处理不了,转给上级... 经理处理不了,转给上级... 总监批准了 王五 的10天假期 === 测试4:请假20天 === 组长处理不了,转给上级... 经理处理不了,转给上级... 总监也处理不了,请假时间太长,拒绝 ``` 代码的关键点: 1. **每个处理者只关心自己能不能处理**。组长只管3天以内的,不管其他;经理只管3到7天的,不管其他。职责非常清晰。 2. **处理不了就传给下一个**。通过`next_`指针,形成链条。请求沿着链条传递,直到有人能处理为止。 3. **链条由客户端组装**。`main()`函数负责创建处理者并串成链条。这意味着你可以灵活调整链条顺序,甚至动态改变链条结构。 4. **扩展容易**。要加HR审批?加一个`HRHandler`类,插到链条里就行,不用改现有代码。 ## 四、优缺点与使用场景 **优点:** **1. 职责单一,代码清晰。** 每个处理者只处理自己负责的请求,逻辑简单明了。不再是几百行的if-else怪物。 **2. 解耦请求和处理。** 调用者不需要知道谁来处理,只需要把请求扔给链条。处理者也不需要知道链条的全貌,只需要关心自己的职责。 **3. 扩展容易。** 要加新规则?加一个新的处理者类,插到链条里就行。不用修改现有代码,符合开闭原则。 **4. 灵活调整处理顺序。** 客户端可以动态调整链条顺序,甚至运行时改变链条结构。比如某些特殊请求跳过某些处理者。 **缺点:** **1. 请求可能没人处理。** 如果链条上所有处理者都处理不了,请求就会到达链条末尾,没有任何处理。需要在链条末尾加一个兜底处理者,或者让客户端检查结果。 **2. 性能开销。** 请求要沿着链条传递,可能经过多个处理者才找到能处理的。链条太长会影响性能。 **3. 调试困难。** 请求在链条中传递,调试时需要跟踪整个链条的执行过程。链条太长,调试会比较痛苦。 **4. 可能形成循环。** 如果不小心让链条形成环,请求会无限循环传递,导致栈溢出。需要在组装链条时避免这种情况。 **适合用的场景:** **1. 审批流程。** 请假审批、报销审批、采购审批等,不同金额或天数由不同级别的人审批。 **2. 异常处理。** 不同级别的异常由不同的处理者处理。INFO级别记日志,WARNING级别发邮件,ERROR级别发短信告警。 **3. 过滤器链。** Web框架中的请求过滤器,多个过滤器依次处理请求。比如先检查权限,再检查参数,最后处理业务逻辑。 **4. 事件处理。** GUI框架中的事件处理,多个监听器依次处理事件。比如鼠标点击事件,先触发A监听器,再触发B监听器。 **不适合用的场景:** **1. 只有一个处理者。** 如果只有一个处理者,用责任链就是多此一举,直接用if-else更简单。 **2. 处理逻辑简单。** 如果处理逻辑很简单,就几个if-else,用责任链反而增加复杂度。 **3. 性能极度敏感。** 链条太长会影响性能,在对性能要求极高的场景(如实时系统)中要慎用。 ## 五、实现要点 **1. 链条末尾要有兜底处理。** 要么让最后一个处理者处理所有剩余的请求,要么在客户端检查请求是否被处理。不要让请求"石沉大海"。 **2. 避免链条形成环。** 组装链条时要小心,不要让next指针形成循环。可以在setNext方法中加检查逻辑。 **3. 链条长度要控制。** 链条太长会影响性能和可维护性。如果链条超过10个节点,可能需要重新审视设计。 **4. 可以用递归或循环实现。** 上面的例子用的是递归调用(next_->handleRequest),也可以用循环实现。递归代码更简洁,但链条太长可能导致栈溢出。 **5. 处理者可以复用。** 同一个处理者可以在不同的链条中复用。比如GroupLeader既可以在请假审批链条中,也可以在加班审批链条中。 ## 六、总结 责任链模式的核心就一句话:**把处理者串成一条链,请求沿着链传递,每个处理者决定自己能不能处理,不能就传给下一个。** 它解决的是"多个处理者谁能处理"的问题。不用设计模式,你可能会写一堆if-else;用责任链模式,每个处理者只管自己的事,职责清晰,扩展容易。 责任链模式在实际开发中应用很广泛。审批系统、异常处理、过滤器链、事件监听……背后都有责任链的影子。很多时候你没意识到自己在用责任链,但它确实在默默帮你解耦请求和处理。