精读C++20设计模式——行动型设计模式:责任链
精读C++20设计模式——行动型设计模式:责任链
前言
责任链将会是我们的第一个行动型设计模式。我需要强调的是——这一蔟设计模式实际上谈不上有更加集合的设计意图,更多的像是补足程序设计除了创建和静态架构本身,剩下的动态运行交互的部分。责任链就隶属于此。
什么是责任链
责任链是这样的一种追责模式:,我们将待处理的对象投射到一个由处理句柄Processor组成的链条,每一个链条可以处理转发进入的对象,可以拦截对象,可以坐视不管对象,总之,走完这个处理流程,我们就处理了对象。
经典的两种责任链的形式,包含指针链和代理链两种,我们都来仔细品味一下。
指针链(Pointer Chain)
在最基础的实现里,责任链往往是指针串联的。也就是说,每一个处理器(Handler)手里都保存着一个 next
指针,指向链条上的下一个节点。当请求到来时,当前节点先处理(或者决定不处理),然后再把请求交给下一个节点,直到责任链的末尾。
#include <iostream>
#include <memory>
#include <string>class Handler {
public:virtual ~Handler() = default;void set_next(std::shared_ptr<Handler> next) { next_ = next; }void handle(const std::string& req) {if (!process(req) && next_) {next_->handle(req);}}protected:virtual bool process(const std::string& req) = 0;private:std::shared_ptr<Handler> next_;
};class AuthHandler : public Handler {
protected:bool process(const std::string& req) override {if (req == "auth") {std::cout << "AuthHandler handled\n";return true;}return false;}
};class LogHandler : public Handler {
protected:bool process(const std::string& req) override {if (req == "log") {std::cout << "LogHandler handled\n";return true;}return false;}
};int main() {auto auth = std::make_shared<AuthHandler>();auto log = std::make_shared<LogHandler>();auth->set_next(log);auth->handle("log"); // LogHandler handledauth->handle("auth"); // AuthHandler handledauth->handle("xxx"); // nobody handles
}
当然,咱们如果不想要按照链表的方式,采用直接映射或者是其他办法,都是可以随意变通的。咱们随时都可以处理和替换上面的链表。但是就需要咱们自己维护。还是有点风险
代理链(Proxy Chain)
另一个常见实现是代理链。它的核心思想是:请求的处理不是节点自己决定“要不要传递”,而是由链式代理类统一调度。每一个节点都被包装成代理,由代理控制“是否调用下一个”。这种方式广泛用于 拦截器(Interceptor)、中间件(Middleware) 等框架中。
#include <iostream>
#include <vector>
#include <functional>class ProxyChain {
public:using Handler = std::function<void(ProxyChain&)>;void add(Handler h) {handlers_.push_back(std::move(h));}void proceed() {if (index_ < handlers_.size()) {auto h = handlers_[index_++];h(*this);}}private:std::vector<Handler> handlers_;size_t index_ = 0;
};int main() {ProxyChain chain;chain.add([](ProxyChain& next){std::cout << "Auth check\n";next.proceed();});chain.add([](ProxyChain& next){std::cout << "Logging\n";next.proceed();});chain.add([](ProxyChain&){std::cout << "Final handler\n";});chain.proceed();
}
输出:
Auth check
Logging
Final handler
代理链显然要比经典的指针链更加的解耦合,我们来让一个完全的代理框架作为控制的一个中心,节点自己只会去负责自己如何实现问题。而且这样的话,咱们不用关心交底动作的实现,可以请求控制中心自己做这个事情。
异步责任链(Async Chain)
在今天,随着CPU核心的快速增加,显然异步是一个非常值得考虑的要素。这里笔者扩展一下异步责任链,实际上没有什么新鲜的。我们只是和 Promise
或 future
的概念结合。我们可以改造一下:
#include <iostream>
#include <future>
#include <vector>
#include <functional>class AsyncChain {
public:using Handler = std::function<std::future<void>()>;void add(Handler h) { handlers_.push_back(std::move(h)); }void run() {std::future<void> fut = std::async(std::launch::async, [this]{for (auto& h : handlers_) {h().get(); // 等待每一步完成}});fut.get();}private:std::vector<Handler> handlers_;
};int main() {AsyncChain chain;chain.add([]{return std::async([]{ std::cout << "Step 1 async\n"; });});chain.add([]{return std::async([]{ std::cout << "Step 2 async\n"; });});chain.run();
}
可以看到,我们的每一个AsyncChain还是那种“链式的分发”(当然可以改成单链表穿起来的形式,这个随意,如果我们的起点关心整体,那咱们就使用std::vector或者是std::list统计起来),但是显然我们不直接等结果,而是等待每一个的异步结果。
其他常见设计变种
环形链(Circular Chain)
环形链的思想很直白:责任链的尾部重新连回链头,形成一个闭环。这样,当一个请求流转到末尾时,它并不会自然结束,而是会继续回到第一个处理器。这个特性使得它天然适合需要循环处理的场景,比如调度器或事件广播。
#include <iostream>
#include <memory>
#include <string>class Handler {
public:Handler(std::string name): name_(std::move(name)) {}void set_next(std::shared_ptr<Handler> next) { next_ = next; }void handle(const std::string& req, int round) {std::cout << name_ << " processing " << req << " (round " << round << ")\n";if (round > 0 && next_) {next_->handle(req, round - 1);}}
private:std::string name_;std::shared_ptr<Handler> next_;
};int main() {auto a = std::make_shared<Handler>("A");auto b = std::make_shared<Handler>("B");auto c = std::make_shared<Handler>("C");a->set_next(b);b->set_next(c);c->set_next(a); // 环形a->handle("task", 5);
}
这里的代码中,链条是 A -> B -> C -> A
的循环,每个节点都会处理任务,并且我们人为设置了 round
来中止循环。环形责任链的迭代意义在于,它从单向链表的形式演化为循环结构,可以持续“传球”直到任务完成或条件满足。
树形链(Tree Chain)
责任链不一定是直线型的,它也可以扩展成一棵树。在 GUI 系统里,事件常常会从根窗口逐层传递到子控件,又可能“冒泡”返回上层,这就是树形责任链的一个典型例子。
#include <iostream>
#include <vector>
#include <memory>
#include <string>class Node {
public:Node(std::string name): name_(std::move(name)) {}void add_child(std::shared_ptr<Node> child) { children_.push_back(child); }void handle(const std::string& req) {std::cout << name_ << " received " << req << "\n";for (auto& child : children_) {child->handle(req);}}
private:std::string name_;std::vector<std::shared_ptr<Node>> children_;
};int main() {auto root = std::make_shared<Node>("Root");auto panel = std::make_shared<Node>("Panel");auto button = std::make_shared<Node>("Button");auto textbox = std::make_shared<Node>("Textbox");root->add_child(panel);panel->add_child(button);panel->add_child(textbox);root->handle("click");
}
在这个例子中,请求像水一样“流淌”到所有子节点。代码演化的意义是:我们从链表结构走向树形结构,责任链的扩展性更强,但同时控制流也更复杂。
策略链(Chain + Strategy)
如果我们希望责任链节点更加灵活,可以在运行时决定使用哪种处理逻辑,就会自然引入策略模式。每个节点内部并不绑定具体的处理逻辑,而是持有一个可变的策略。
#include <iostream>
#include <functional>
#include <memory>class Handler {
public:Handler(std::function<bool(const std::string&)> strat): strategy_(std::move(strat)) {}void set_next(std::shared_ptr<Handler> next) { next_ = next; }void handle(const std::string& req) {if (!strategy_(req) && next_) {next_->handle(req);}}
private:std::function<bool(const std::string&)> strategy_;std::shared_ptr<Handler> next_;
};int main() {auto auth = std::make_shared<Handler>([](const std::string& req){if (req == "auth") { std::cout << "auth ok\n"; return true; }return false;});auto log = std::make_shared<Handler>([](const std::string& req){if (req == "log") { std::cout << "logged\n"; return true; }return false;});auth->set_next(log);auth->handle("log");
}
这种演化让责任链更加动态化,策略函数相当于“插拔式”的逻辑。迭代意义是我们不再需要为每个节点单独写一个类,而是将处理逻辑解耦出来,形成可组合的责任链。
可回溯责任链(Reversible Chain)
最后一个变种是支持回退的责任链。当某个节点处理失败时,可以沿着链路回退到前面节点执行补偿操作。这在事务系统里非常常见。
#include <iostream>
#include <vector>
#include <functional>class ReversibleChain {
public:using Action = std::function<bool()>; // 正向using Undo = std::function<void()>; // 反向void add(Action act, Undo undo) {steps_.push_back({std::move(act), std::move(undo)});}void run() {size_t executed = 0;for (; executed < steps_.size(); ++executed) {if (!steps_[executed].first()) break;}if (executed < steps_.size()) {std::cout << "rollback...\n";for (int i = executed - 1; i >= 0; --i) {steps_[i].second();}}}private:struct Step {Action first;Undo second;};std::vector<Step> steps_;
};int main() {ReversibleChain chain;chain.add([]{ std::cout << "step1 ok\n"; return true; },[]{ std::cout << "undo1\n"; });chain.add([]{ std::cout << "step2 fail\n"; return false; },[]{ std::cout << "undo2\n"; });chain.run();
}
这段代码模拟了事务处理:前两个步骤执行时,第二步失败触发回退机制。它的演化意义是责任链不仅支持“正向传递”,也支持“反向回溯”,增强了鲁棒性。
总结
责任链处理什么问题?
责任链要解决的核心问题是:当请求需要被多个处理者依次尝试时,我们不想在调用方显式地写死“谁来处理”。如果调用方必须了解所有的处理逻辑并逐个调用,那么系统会高度耦合且不利于扩展
责任链如何解决上面的问题?
责任链的解决方式很优雅:把处理逻辑抽象为节点,并把节点通过链式结构组织起来。调用方只需要把请求交给链的起点,链会自动沿着节点传递下去,直到某个节点完成处理或到达终点为止。这样,调用方和具体处理者就彻底解耦,链条的组合方式也可以在运行时自由变化。
各种变种的优劣对比
变种 | 优点 | 缺点 | 典型做法/应用场景 |
---|---|---|---|
指针链 | 实现直观、轻量、灵活替换节点 | 链路长时不易追踪,组织逻辑要手动维护 | 经典的 Handler->set_next() 模式 |
代理链 | 请求调度交给框架统一管理,节点只专注业务逻辑 | 节点失去部分“是否传递”的自主性,依赖代理机制 | 中间件框架、拦截器、过滤器 |
环形链 | 天然适合循环调度和轮询,能保证请求持续传递 | 必须设置中止条件,否则可能陷入无限循环 | 调度器、轮询器、事件广播 |
树形链 | 支持多路分发,能表达复杂的层次化逻辑(如 GUI 事件) | 结构复杂,请求流向和调试难度更高 | GUI 事件冒泡、日志多目的地 |
策略链 | 节点逻辑可动态替换,支持插拔式策略组合 | 层次感增加,逻辑分布在策略函数中,阅读成本提升 | 规则引擎、权限校验 |
异步责任链 | 自然支持异步流程,避免阻塞,适配现代异步框架 | 控制流复杂,需要处理异常、超时和上下文传递 | Web 中间件、IO 任务链 |
可回溯责任链 | 支持回退/补偿操作,适合事务场景,鲁棒性高 | 实现复杂,需要保存上下文和反向操作 | 数据库事务、分布式 Saga、撤销/重做 |