当前位置: 首页 > news >正文

精读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核心的快速增加,显然异步是一个非常值得考虑的要素。这里笔者扩展一下异步责任链,实际上没有什么新鲜的。我们只是和 Promisefuture 的概念结合。我们可以改造一下:

#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、撤销/重做
http://www.dtcms.com/a/426855.html

相关文章:

  • transformers音频实战01-音频概念
  • 方寸网站建设如何建立免费个人网站
  • Spring Boot 实战 Redis 分布式锁:从原理到高并发落地
  • nodejs做网站的弊端马来西亚网站后缀
  • CSDN Markdown 编辑器快捷键大全
  • 基于GNS3 web UI配置RIP协议(Wireshark 分析)
  • Helm Chart 中,SeaweedFS的 master.data.type 选择
  • 智能座舱问答
  • kube-prometheus监控服务发现
  • 攻防世界-Web-Web_python_template_injection
  • seo站内优化公司河北邯郸seo网站建设网站优化
  • wordpress网站插件优秀校园网站
  • Hibernate批量查询方法全面解析
  • 深度解析 ChatGPT 和 Claude 的记忆机制
  • 994. 腐烂的橘子,207. 课程表, 208.实现 Trie (前缀树)
  • 有趣的化学元素
  • 深圳网站建设者西安广告公司
  • READ_ONCE、smp_store_release在io_uring中实例分析
  • C/C++数据结构之用数组实现栈
  • Linux timekeeping
  • macOS 下安装 zsh、zsh-syntax-highlighting、powerlevel9k、nerd-font
  • CarveMe:代谢模型构建
  • windows显示驱动开发-调试间接显示驱动程序(二)
  • 企业平台网站建设制作一个网站平台
  • LinuxC++——etcd分布式键值存储系统入门
  • 使用arcgis提取评价指标时,导出数据是负数-9999
  • VUE3+element plus 实现表格行合并
  • LinuxC++——etcd分布式键值存储系统API(libetcd-cpp-api3)下载与二次封装
  • Electron vue项目 打包 exe文件2
  • 【开题答辩全过程】以 springboot高校创新创业课程体系的设计与实现为例,包含答辩的问题和答案