从C++编程入手设计模式——责任链模式

​ 当我们的一个请求需要多个对象去处理,但具体由谁来处理,是根据情况动态决定的。例如,一个日志系统中,可能希望把错误信息写入文件,把提示信息输出到控制台,而不是每个消息都写到所有地方。再比如,用户输入的表单需要经过多个字段的验证,每个验证器处理一项,验证失败就可以中断。

​ 面对这些问题,我们希望有一种方式,让多个处理者自动接力,把请求传下去,直到有人愿意接住它,或者最终没人处理。这个思路就是“责任链模式”。

​ 责任链模式的核心思想是:将多个处理者连接成一条链,每个处理者有机会处理请求,如果不能处理就交给下一个。使用这个模式可以做到请求发送者和处理者之间的解耦,不需要写死“谁来处理”这件事。

​ 责任链的结构非常简单,处理器之间形成一个链条:

[ 请求 ]↓
[Handler1]───→[Handler2]───→[Handler3]↑              ↑              ↑│ 判断是否处理  │ 判断是否处理  │ 判断是否处理

​ 每个 Handler 有机会决定是否处理当前请求。如果处理就完成了;如果不处理,就把请求传递给下一个 Handler。

​ 在代码模板上,看起来就是这样的:

#include <iostream>
#include <memory>
#include <string>// 抽象处理者
class Handler {
protected:std::shared_ptr<Handler> next_;
public:virtual ~Handler() = default;void setNext(std::shared_ptr<Handler> next) {next_ = std::move(next);}virtual void handle(const std::string& request) {if (next_) {next_->handle(request);}}
};// 具体处理者A
class ConcreteHandlerA : public Handler {
public:void handle(const std::string& request) override {if (request == "A") {std::cout << "ConcreteHandlerA handled request: " << request << '\n';} else {Handler::handle(request);}}
};// 具体处理者B
class ConcreteHandlerB : public Handler {
public:void handle(const std::string& request) override {if (request == "B") {std::cout << "ConcreteHandlerB handled request: " << request << '\n';} else {Handler::handle(request);}}
};int main() {auto handlerA = std::make_shared<ConcreteHandlerA>();auto handlerB = std::make_shared<ConcreteHandlerB>();handlerA->setNext(handlerB);handlerA->handle("A");handlerA->handle("B");handlerA->handle("C");return 0;
}

​ 或者这里有一个看起来更加具体的版本:

#include <iostream>
#include <memory>
#include <string>struct Message {enum Type {INFO,WARNING,ERROR};Type type;std::string content;Message(Type t, std::string c): type(t), content(std::move(c)) {}
};// 抽象处理器
struct Handler {std::shared_ptr<Handler> next;void setNext(std::shared_ptr<Handler> n) {next = n;}void handle(const Message& msg) {if (canHandle(msg)) {process(msg);} else if (next) {next->handle(msg);}}virtual ~Handler() = default;protected:virtual bool canHandle(const Message& msg) = 0;virtual void process(const Message& msg) = 0;
};// 文件处理器
struct FileLogger : Handler {
protected:bool canHandle(const Message& msg) override {return msg.type == Message::ERROR;}void process(const Message& msg) override {std::cout << "[File] " << msg.content << '\n';}
};// 控制台处理器
struct ConsoleLogger : Handler {
protected:bool canHandle(const Message& msg) override {return msg.type == Message::INFO;}void process(const Message& msg) override {std::cout << "[Console] " << msg.content << '\n';}
};// 弹窗处理器
struct GuiLogger : Handler {
protected:bool canHandle(const Message& msg) override {return msg.type == Message::WARNING;}void process(const Message& msg) override {std::cout << "[Popup] " << msg.content << '\n';}
};

实际应用场景

责任链模式并不仅限于日志处理,它在以下场景中也非常常见:

  • 表单字段校验(每个字段一个处理器)
  • 网络请求拦截器(认证、权限、压缩等)
  • 中间件系统(如 Web 框架中的中间件链)
  • 命令解释器或事件分发器

它的强大之处在于:每一个节点都可以自行决定是否处理、是否继续传递,这为灵活的处理流程提供了极大的自由度。

与其他设计模式的对比

责任链模式与某些模式在结构上可能相似,但其意图和应用方式有明显不同。

与策略模式的区别:

策略模式侧重于“选择一个方案”,它要求用户明确设置当前使用哪一个策略。而责任链模式强调“顺序处理、传递判断”,调用者通常不关心由谁处理,处理者也可不止一个。策略是点选一个方案,责任链是顺着链找合适的方案。

与装饰器模式的区别:

装饰器模式是“增强已有功能”,它强调在不修改原有对象的基础上添加附加行为;而责任链模式更像“责任传递”,强调每个处理器只处理自己关心的部分,然后把任务交出去。两者都用了链式结构,但目的不同。

与观察者模式的区别:

观察者模式是“一对多”的事件广播,所有订阅者都能收到通知;而责任链是“一对一逐步传递”,请求只有一个接收者(或多个逐步处理者),而且是按顺序决定。


责任链的变体设计

虽然标准的责任链模式是单向串联、一次只处理一个处理器,但在实际应用中,可以根据业务需要进行一些“变体”设计。

1. 所有处理器都参与处理
默认责任链遇到能处理的就停止传递,但有时我们希望每个处理器都能处理自己的部分,不影响后续。例如,在一个网页渲染系统中,每个处理器可以对页面对象做一定处理,最终效果是叠加的。

这时候我们可以取消 if...else 的限制,统一调用 handle(),不提前终止。

2. 动态重排链条顺序
有些场景下,我们希望链的顺序可以动态调整,比如根据配置文件加载不同顺序的处理器、按优先级排序等。这时候链条的构造阶段就变得非常重要,链不再是硬编码。

3. 支持回溯或逆向传递
有些业务希望在“正向处理”结束后,再做一次“逆向清理”或“结果回传”。比如在中间件系统中,请求是从前往后传递,响应是从后往前传递的。这种双向链的设计可以在责任链上加一个“after”阶段。

练习题:设计一个按日志类型分类的责任链处理系统

题目描述:
请设计一个日志处理系统,它能够根据日志的类型选择合适的处理方式进行输出。系统中包含三种日志类型:

  • DISK:需要写入磁盘
  • CONSOLE:需要输出到控制台
  • GUI_SCREEN:需要在图形界面上显示

每种日志类型应该由专门的处理器进行处理,系统应当自动根据日志类型选择合适的处理器完成输出。

要求:

  1. 定义一个 Message 结构体,表示一条日志信息,应包含日志类型(枚举)和内容。
  2. 定义一个抽象基类 Handler,表示日志处理器。该类应包含判断能否处理当前日志的方法 canAccept,和实际处理日志的方法 processSessions
  3. 实现三个具体的处理器类:DiskHandler, ConsoleHandler, GuiHandler,它们分别处理各自类型的日志。
  4. 创建一个 HandlerChain 类,作为责任链的管理者,负责按顺序传递日志给合适的处理器。
  5. 要求每条日志只由能够处理它的处理器执行一次,不可重复处理。
  6. 使用 C++ 标准库中的 std::list 或类似结构管理处理器链条。
  7. 使用 std::println(C++23)输出日志内容,格式如:
    From Console: some info
    From Disk: critical error

拓展选做:

  • 支持动态添加处理器。
  • 如果没有合适的处理器,输出 "No handler for message type"
  • 保证处理器的析构为虚函数。

modern-cpp-patterns-playground/ResponsibilityChain/OutputHandler at main · Charliechen114514/modern-cpp-patterns-playground

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88139.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88139.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/88139.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

泛型方法调用需要显示指定泛型类型的场景

泛型类型的推断确定 一般来说&#xff0c;泛型类型的推断可以由以下几个场景确定&#xff1a; 变量定义指定类型 List<String> strList new ArrayList<>();ArrayList的泛型类型是依据变量的类型确定的。 方法返回值确定 Overridepublic Function<List<I…

Deep Research:开启深度研究的智能新时代

在当今信息爆炸的时代&#xff0c;人们面临着海量的信息&#xff0c;无论是专业人士还是普通消费者&#xff0c;都迫切需要一种高效、精准的方式来获取和分析信息。OpenAI 推出的 Deep Research&#xff0c;宛如一颗璀璨的新星&#xff0c;在知识的海洋中为我们导航&#xff0c…

曼昆《经济学原理》第九版 宏观经济学 第二十四章失业与自然失业率

以下是曼昆《经济学原理》第九版宏观经济学第二十四章**“失业与自然失业率”**的详细讲解&#xff0c;从零基础开始构建知识框架&#xff0c;结合中国实际案例与生活化比喻&#xff0c;帮助小白系统理解核心概念&#xff1a; 一、知识框架&#xff1a;失业的“全景图” 1. 核…

【软考高级系统架构论文】论软件系统架构风格

论文真题 请以“软件系统架构风格”为论题,依次从以下三个方面进行论述: 1、概要叙述你参与分析和开发的软件系统开发项目以及你所担任的主要工作。 2、分析软件系统开发中常用的软件系统架构风格有哪些?详细阐述每种风格的具体含义。 3、详细说明在你所参与的软件系统开发项…

LeetCode--35.搜索插入位置

解题思路&#xff1a; 1.获取信息&#xff1a; 给定一个升序排列的数组和一个整数&#xff0c;要求查找该整数应该在数组中插入的位置 限制条件是&#xff0c;要求时间复杂度为O(log N) 2.分析题目&#xff1a; 时间复杂度要求O(log N)&#xff0c;那么就使用二分查找法&#x…

Unix、Linux、POSIX、Minix 区别与联系

一、Unix&#xff1a;现代操作系统的技术原型 诞生&#xff1a;1969年贝尔实验室&#xff0c;用C语言重写后实现跨平台&#xff08;1973年&#xff09;。核心设计&#xff1a; 一切皆文件&#xff08;设备/进程均抽象为文件&#xff09;。管道&#xff08;|&#xff09;和文本…

python计算长方形的周长 2025年3月青少年电子学会等级考试 中小学生python编程等级考试一级真题答案解析

python计算长方形的周长 2025年3月 python编程等级考试一级编程题 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】 1、Python比赛 信息素养大赛Python编程挑战赛 蓝桥杯python选拔赛真题详解 蓝桥杯python省赛真题详解 蓝桥杯python国赛真题详解 2、Python考级 p…

使用 RedisVL 进行复杂查询

一、前置条件 在开始之前&#xff0c;请确保&#xff1a; 已安装 redisvl 并激活相应的 Python 环境。运行 Redis 实例&#xff0c;且 RediSearch 版本 > 2.4。 二、初始化与数据加载 我们将使用一个包含用户信息的数据集&#xff0c;字段包括 user、age、job、credit_s…

「Linux文件及目录管理」vi、vim编辑器

知识点解析 vi/vim编辑器简介 vi:Linux默认的文本编辑器,基于命令行操作,功能强大。vim:vi的增强版,支持语法高亮、多窗口编辑、插件扩展等功能。vi/vim基本模式 命令模式:默认模式,用于移动光标、复制、粘贴、删除等操作。插入模式:按i进入,用于输入文本。末行模式:…

电容器保护测控装置如何选型?

在电力系统的无功补偿环节&#xff0c;​电容器保护测控装置是保障并联电容器组安全稳定运行的核心设备。其选型需综合考量保护需求、系统环境及扩展功能。以下是关键选型要素分析&#xff1a; ​一、明确核心功能需求​ 电容器保护测控装置&#xff0c;选型时需匹配电容器组实…

最近小峰一直在忙国际化项目,确实有点分身乏术... [特殊字符] 不过! 我正紧锣密鼓准备一系列干货文章/深度解析

本人详解 大家晚上好呀&#xff01;&#x1f319; 最近小峰一直在忙国际化项目&#xff0c;确实有点分身乏术... &#x1f605; 不过&#xff01; 我正紧锣密鼓准备一系列干货文章/深度解析&#xff08;选一个更符合你内容的词&#xff09;&#xff0c;很快就会和大家见面啦&am…

OpenCV CUDA模块设备层-----设备端(GPU)线程块级别的一个内存拷贝工具函数blockCopy()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在同一个线程块&#xff08;thread block内&#xff0c;将 [beg, end) 范围内的数据并行地复制到 out 开始的位置。 它使用了 CUDA 线程协作机制…

https没有证书可以访问吗?外网怎么访问内网?

没有SSL证书的网站无法正常通过HTTPS协议访问‌。HTTPS的实现必须依赖有效的SSL证书完成加密握手&#xff0c;否则浏览器会直接阻断连接或显示严重的安全警告。‌‌ 一、技术实现层面‌ ‌HTTPS协议强制要求证书‌。 HTTPS基于SSL/TLS协议实现加密通信&#xff0c;而SSL证书是…

Python pytesseract【OCR引擎库】 简介

想全面了解DeepSeek的看过来 【包邮】DeepSeek全攻略 人人需要的AI通识课 零基础掌握DeepSeek的实用操作手册指南【限量作者亲笔签名版售完即止】 玩转DeepSeek这本就够了 【自营包邮】DeepSeek实战指南 deepseek从入门到精通实用操作指南现代科技科普读物AI普及知识读物人工智…

ubuntu安装postman教程并中文汉化详细教程

一、下载postman安装包 通过网盘分享的文件:Postman-linux-x64-8.7.0.tar.gz 链接: https://pan.baidu.com/s/10WYeguDJlK85cKJ6ptX01w?pwd=xqkh 提取码: xqkh 二、解压到/opt目录 tar -zxvf Postman-linux-x64-8.7.0.tar.gz如果子用户没有/opt权限,可以给子用户赋予/opt的…

《垒球知识科普》垒球世界纪录·垒球1号位

奥运垒球冠军记录 历届冠军榜 1996亚特兰大奥运会 冠军&#xff1a;美国队 ⚡ 首届奥运垒球赛&#xff0c;美国主场3战全胜夺冠&#xff01; 1996 Atlanta Olympics Champion: USA ⚡ Dominated all 3 games in first Olympic softball event 2000悉尼奥运会 冠军&#…

通信网络编程3.0——JAVA

主要添加了私聊功能 1服务器类定义与成员变量 public class ChatServer {int port 6666;// 定义服务器端口号为 6666ServerSocket ss;// 定义一个 ServerSocket 对象用于监听客户端连接//List<Socket> clientSockets new ArrayList<>();// 定义一个列表用于存储…

RediSearch `FT.CREATE` 完全参数指南 HASH/JSON 双写实战

1、索引与 Schema 速概 索引 (index) —— 倒排、前缀、向量、Geo … 元数据集合Schema —— 索引蓝图&#xff1a;定义字段、类型、权重、排序及存储策略FT.CREATE —— 创建索引命令&#xff0c;分「索引级参数」和「字段级参数」两层 2 、FT.CREATE 语法模板 FT.CREATE &…

QT学习教程(三十七)

系统繁忙时的响应&#xff08;Staying Responsive During Intensive Processing&#xff09; 当我们调用QApplication::exec()时&#xff0c;Qt 就开始了事件循环。启动时&#xff0c;Qt 发出显示和绘制事件&#xff0c;把控件显示出来。然后&#xff0c;事件循环就开始了&…

hot100 -- 17.技巧

1.多数元素 问题&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 方法1&#xff1a; 哈希表 实时判断&#xff…