C++ 设计模式 - 访问者模式
一:概述
访问者模式将作用于对象层次结构的操作封装为一个对象,并使其能够在不修改对象层次结构的情况下定义新的操作。
《设计模式:可复用面向对象软件的基础》一书中的访问者模式因两个原因而具有传奇色彩:一是因为它的复杂性,二是因为它使用了一种名为“双重分派”的技术。双重分派指的是根据对象和函数参数选择成员函数的过程。当然,访问者模式的复杂性主要在于 C++ 本身不支持双重分派。在讨论单分派和双重分派之前,让我先谈谈访问者模式。
什么是“双重分派”?双重分派(Double Dispatch)是一个动态选择调用方法的机制,其中被调用的具体函数依赖于两个对象的运行时类型,而不仅仅是一个对象的类型。这在多态编程中尤其有用,能够实现复杂的行为决策。在 C++ 中,双重分派通常通过虚函数和函数重载的组合实现,例如在访问者模式中。
二:使用场景
1. 操作应该在对象层次结构上执行。
2. 操作变更频繁。
3. 对象层次结构是稳定的。
三:类结构设计

-
Visitor 类:在对象结构上定义访问操作(
visit方法),用来描述对象结构中各元素可能执行的行为。 -
ConcreteVisitor 类:实现 Visitor 接口中的具体访问操作,为不同的元素提供特定的处理逻辑。
-
Element 类:表示对象结构中的元素,定义了
accept方法,用于接收Visitor的操作请求。 -
ConcreteElement 类:具体的对象结构元素,实现
accept方法,将自己作为参数传递给访问者,以完成双向的操作绑定。
四:一个具体的例子

访问者模式有两种类型的层次结构:对象层次结构(CarElement)和操作层次结构(CarElementVisitor)。对象层次结构相对稳定,但操作层次结构可能需要支持新操作。CarElement 和 CarElementVisitor 都充当接口,这意味着每个具体的汽车元素(如 Wheel、Engine、Body 和 Car)都必须实现 accept(CarElementVisitor) 成员函数。相应地,每个具体的操作(如 CarElementDoVisitor 和 CarElementPrintVisitor)都必须实现四个重载的 visit(Wheel)、visit(Engine)、visit(Body) 和 visit(Car) 方法。
假设操作 CarElementPrintVisitor 被应用于对象层次结构。CarElementPrintVisitor 的任务可能是打印被访问的汽车零部件的名称。首先,像 Engine 这样的汽车元素接受访问者(accept(CarElementVisitor)),并使用访问者通过 visitor.visit(this) 调用操作层次结构,将自身作为参数传递。这确保了调用 CarElementPrintVisitor 上的 visit(Engine) 重载。访问 Car 是特殊的,因为 Car 由多个汽车元素组成。因此,Car 的 accept 成员函数将接受调用委托给它的所有汽车零部件。
关于访问者的一个关键特征是:它依赖于两个对象,决定执行什么操作:访问者和被访问的对象。
五:代码示例
#include <iostream>
#include <string>
#include <vector>// 前向声明 Visitor 和元素类
class CarElementVisitor;class CarElement {
public:// 接受访问者操作的接口,由具体元素实现virtual void accept(CarElementVisitor& visitor) const = 0;virtual ~CarElement() = default;
};// 前向声明具体元素类
class Body;
class Car;
class Engine;
class Wheel;// 访问者接口,定义了针对不同元素的访问操作
class CarElementVisitor {
public:// 针对不同具体元素的访问操作的虚函数virtual void visit(Body body) const = 0;virtual void visit(Car car) const = 0;virtual void visit(Engine engine) const = 0;virtual void visit(Wheel wheel) const = 0;virtual ~CarElementVisitor() = default;
};// 轮胎类,表示汽车的一个轮胎
class Wheel : public CarElement {
public:// 构造函数,接收轮胎的名称Wheel(const std::string& n) : name(n) { }// 接受访问者,调用访问者的 visit(Wheel) 方法void accept(CarElementVisitor& visitor) const override {visitor.visit(*this);}// 获取轮胎的名称std::string getName() const {return name;}
private:std::string name; // 轮胎名称
};// 车身类,表示汽车的车身
class Body : public CarElement {
public:// 接受访问者,调用访问者的 visit(Body) 方法void accept(CarElementVisitor& visitor) const override {visitor.visit(*this);}
};// 发动机类,表示汽车的发动机
class Engine : public CarElement {
public:// 接受访问者,调用访问者的 visit(Engine) 方法void accept(CarElementVisitor& visitor) const override {visitor.visit(*this);}
};// 汽车类,表示一辆汽车,由多个汽车元素组成
class Car : public CarElement {
public:// 构造函数,接收一个汽车元素列表Car(std::initializer_list<CarElement*> carElements) : elements{ carElements } {}// 接受访问者,依次让每个汽车元素接受访问void accept(CarElementVisitor& visitor) const override {for (auto elem : elements) {elem->accept(visitor); // 委托访问操作给子元素}visitor.visit(*this); // 最后访问汽车本身}
private:std::vector<CarElement*> elements; // 汽车元素列表
};// 执行动作的访问者类,定义具体的访问行为
class CarElementDoVisitor : public CarE
六:相关设计模式
- 稳定的对象层次结构通常会应用 组合模式(Composite Pattern)。
- 迭代器模式(Iterator Pattern) 通常用于遍历对象层次结构。
- 新的元素可以通过创建型模式(例如 工厂方法(Factory Method) 或 原型模式(Prototype Pattern))来创建
七:优缺点
优点:
- 可以轻松地向操作层次结构中添加新的操作(访问者)。
- 一个操作可以被封装在一个访问者中。
- 在遍历对象层次结构时,可以构建和维护状态。
缺点:
- 如果需要在对象层次结构中添加新的被访问对象(VisitedObject),修改会非常困难。
- 需要将新的被访问对象(VisitedObject)添加或从对象层次结构中移除。
- 需要扩展访问者的接口,并在每个具体访问者中添加或移除
visit(VisitedObject)成员函数。
八:参考:
1. The Visitor Pattern – MC++ BLOG
2. https://commons.wikimedia.org/w/index.php?curid=122709059
相关文章:
C++ 设计模式 - 访问者模式
一:概述 访问者模式将作用于对象层次结构的操作封装为一个对象,并使其能够在不修改对象层次结构的情况下定义新的操作。 《设计模式:可复用面向对象软件的基础》一书中的访问者模式因两个原因而具有传奇色彩:一是因为它的复杂性&a…...
海云安开发者智能助手(D10)全面接入DeepSeek,赋能开发者安全高效编码新范式
海云安正式宣布完成与DeepSeek(深度求索)的深度技术融合,旗下核心产品D10开发者智能助手全面接入DeepSeek R1模型。此次合作标志着海云安在"AI驱动开发安全"领域实现重要突破。数据显示,通过DeepSeek R1模型的优化与蒸馏…...
服务器绑定 127.0.0.1 和 0.0.0.0 的区别
前言 IP 地址实际上并不是分配给计算机的,而是分配给网卡的,因此当计算机上存在多块网卡时,每一块网卡都会有自己的 IP 地址。 绑定 127.0.0.1 是绑定到 lookback 这个虚拟的本地回环接口,该接口只处理本机上的数据,…...
ML.NET库学习005:基于机器学习的客户细分实现与解析
文章目录 ML.NET库学习005:基于机器学习的客户细分实现与解析项目主要目的和原理目的原理 项目概述实现的主要功能主要流程步骤使用的主要函数方法关键技术 主要功能和步骤功能详细解读详细步骤解析 数据集及其处理步骤数据集处理步骤关键处理步骤原理1. 数据清洗与…...
分布式id探索
一、为什么要使用分布式id? 随着数据量增加,数据需要进行水平拆分,但表自增id无法满足唯一性; 二、分布式id的特点 1唯一性 2 趋势递增、单调递增(数据库中存放的数据结构数据从小到大有序排列)࿰…...
互联网协议套件中的服务类型(RFC 1349)技术解析与总结
1. 背景与核心目标 RFC 1349 是对 IP 协议头部 服务类型(Type of Service, TOS)字段语义的更新与澄清文档,发布于 1992 年。其主要目标包括: 重新定义 TOS 字段的用途:明确 TOS 字段的语义,解决历史标准中的…...
java-初识List
List: List 是一个接口,属于 java.util 包,用于表示有序的元素集合。List 允许存储重复元素,并且可以通过索引访问元素。它是 Java 集合框架(Java Collections Framework)的一部分 特点: 有序…...
【Linux系统】—— 简易进度条的实现
【Linux系统】—— 简易进度条的实现 1 回车和换行2 缓冲区3 进度条的准备代码4 第一版进度条5 第二版进度条 1 回车和换行 先问大家一个问题:回车换行是什么,或者说回车和换行是同一个概念吗? 可能大家对回车换行有一定的误解࿰…...
一文学会:用DeepSeek R1/V3 + AnythingLLM + Ollama 打造本地化部署的个人/企业知识库,无须担心数据上传云端的泄露问题
文章目录 前言一、AnythingLLM 简介&基础应用1.主要特性2.下载与安装3.配置 LLM 提供商4.AnythingLLM 工作区&对话 二、AnythingLLM 进阶应用:知识增强使用三、AnythingLLM 的 API 访问四、小结1.聊天模式2.本地存储&向量数据库 前言 如果你不知道Olla…...
开源身份和访问管理方案之keycloak(一)快速入门
文章目录 什么是IAM什么是keycloakKeycloak 的功能 核心概念client管理 OpenID Connect 客户端 Client Scoperealm roleAssigning role mappings分配角色映射Using default roles使用默认角色Role scope mappings角色范围映射 UsersGroupssessionsEventsKeycloak Policy创建策略…...
C++STL(六)——list模拟
目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…...
HTML5--网页前端编程(下)
HTML5–网页前端编程(下) 9.常用标签下 (1)表格标签 用来展示数据,显示数据,规整条理,可读性好 基本语法 <table><tr> <td>单元格内的文字</td> <td>单元格内的文字</td>… </tr> <tr> <td>单元格内的文字&l…...
Spring 的 ResponseEntity 包装器使用详解
简介 在 Spring 中,ResponseEntity 是 HTTP 响应的包装器。它允许自定义响应的各个方面: HTTP 状态码 响应主体 HTTP 请求头 使用 ResponseEntity 允许完全控制 HTTP 响应,并且它通常用于 RESTful Web 服务中从控制器方法返回响应。 基…...
Git 分布式版本控制工具使用教程
1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具,由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目,速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰,并且在…...
linux部署ollama+deepseek+dify
Ollama 下载源码 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz启动 export OLLAMA_HOST0.0.0.0:11434 ollama serve访问ip:11434看到即成功 Ollama is running 手动安装deepseek…...
torch_bmm验算及代码测试
文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘,比如, mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…...
Vue3 特点
不强制要求组件有根节点 // vue2 <template><div><h1>标题</h1><p>内容</p></div> </template>// vue3 <template><h1>标题</h1><p>内容</p> </template> 注意事项 虽然 Vue 3 不再强制…...
mysql8 C++源码中创建表函数,表字段最大数量限制,表行最大存储限制
在 MySQL 8 的 C 源码中,表的最大字段数量限制体现在 MAX_FIELDS 宏定义中。这个宏定义了表中可以拥有的最大字段数量。 代码中的体现 在 mysql_prepare_create_table 函数中,有以下代码段检查表的字段数量是否超过最大限制: cpp if (alt…...
CTFHub-RCE系列wp
目录标题 引言什么是RCE漏洞 eval执行文件包含文件包含php://input读取源代码远程包含 命令注入无过滤过滤cat过滤空格过滤目录分隔符过滤运算符综合过滤练习 引言 题目共有如下类型 什么是RCE漏洞 RCE漏洞,全称是Remote Code Execution漏洞,翻译成中文…...
【OneAPI】通过网页预渲染让搜索引擎收录网页
API简介 网页预渲染,适用于动态网页以及单页面的SEO,支持网页缓存。 您无须更改代码即可让搜索引擎收录您的网页。只要将需要预渲染的页面转发的本接口即可。 如果您使用Nginx作为网页服务器,推荐使用以下配置: #您的网站locat…...
从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓
作者:羿莉(萧羿) 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型,以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1,而且在推理模型的成本优化上实现了突破…...
【学习笔记】企业数字化转型顶层设计与企业架构TOGAF9.2-第0章 导论
数据要素资产化迈入关键发展期 围绕发挥数据要素乘数作用,研究实施“数据要素x”行动:从供需两端发力,在智能制造、商贸流通、交通物流、金融服务、医疗健康等若干重点领域,加强场景需求牵引,打通流通障碍、提升供给质量…...
Vue3 Ref全家桶深度解析:掌握响应式编程精髓
Vue3 Ref全家桶深度解析:掌握响应式编程精髓 一、Ref核心概念 1.1 响应式数据容器 const count ref(0) // 相当于创建了一个响应式容器: {value: 0,__v_isRef: true,// 其他响应式系统属性 }1.2 全家桶全景图 #mermaid-svg-VkHPjjlo16rOyItj {font-f…...
如何避免大语言模型中涉及丢番图方程的问题
希尔伯特第十问题是一个著名的数学问题,涉及不定方程(又称为丢番图方程)的可解答性。然而在大模型中,我们希望问题都是确定的可解的,或者说要尽可能的想办法避免不确定的不可解问题。由于丢番图方程问题是不可判定问题(即不存在一个有效的算法能够解决该类问题的所有实例…...
SpringCloud - Sentinel服务保护
前言 该博客为Sentinel学习笔记,主要目的是为了帮助后期快速复习使用 学习视频:7小快速通关SpringCloud 辅助文档:SpringCloud快速通关 源码地址:cloud-demo 一、简介 官网:https://sentinelguard.io/zh-cn/index.h…...
Java 使用腾讯翻译 API 实现含 HTML 标签文本,json值,精准翻译工具
注意:需搭配标题二的腾讯翻译工具使用 一-1、翻译标签文本工具 package org.springblade.common.utils;import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern;public class TencentTranslationFor…...
前端导出pdf,所见即所得
一、推荐方案:html2canvas jsPDF(图片式PDF) javascript import html2canvas from html2canvas; import jsPDF from jspdf;const exportPDF async (elementId, fileName) > {const element document.getElementById(elementId);// 1.…...
单片机上SPI和IIC的区别
SPI(Serial Peripheral Interface)和IC(Inter-Integrated Circuit)是两种常用的嵌入式外设通信协议,它们各有优缺点,适用于不同的场景。以下是它们的详细对比: — 1. 基本概念 SPI࿰…...
03-DevOps-安装并初始化Gitlab
Gitlab可以理解为是自己搭建的GitHub,也就是自己的代码仓库。 开启macvlan 在192.168.1.10服务器上,构建Macvlan网络,这种网络模式可以为每个容器独立分配ip。 docker network create -d macvlan \--subnet192.168.1.0/24 \--ip-range192.16…...
RabbitMQ 从入门到精通:从工作模式到集群部署实战(五)
#作者:闫乾苓 系列前几篇: 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)》:link 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(二)》: lin…...
