​【C++设计模式】第二十二篇:访问者模式(Visitor)

注意:复现代码时,确保 VS2022 使用 C++17/20 标准以支持现代特性。

数据结构与操作的解耦之道


1. 模式定义与用途

核心思想

  • 访问者模式:将数据结构的操作数据结构本身分离,通过访问者对象实现操作逻辑,支持在不修改类的前提下添加新功能。
  • 关键用途
    ​1.动态扩展功能:新增操作无需修改原有类(如导出、序列化、统计)。
    ​2.解耦数据结构与操作:将分散的操作集中到访问者类中。
    ​3.支持复杂对象结构:适用于树形、图形等嵌套结构的统一处理。

经典场景

  • 抽象语法树(AST)遍历(类型检查、代码生成)。
  • 文档导出(HTML、PDF、纯文本)。
  • UI组件渲染(不同平台绘制逻辑)。

2. 模式结构解析

UML类图

+---------------------+          +---------------------+  
|       Element       |          |       Visitor       |  
+---------------------+          +---------------------+  
| + accept(v: Visitor)|<|--------| + visit(e: ElementA)|  
+---------------------+          | + visit(e: ElementB)|  ^                      +---------------------+  |                                ^  +-------+-------+              +---------+---------+  |               |              |                   |  
+---------------------+    +----------------+ +----------------+  
|    ElementA        |    |  ConcreteVisitor | |     Client     |  
+---------------------+    +----------------+ +----------------+  
| + accept(v: Visitor)|    | + visit()      | | 调用访问者处理元素 |  
+---------------------+    +----------------+ +----------------+  

角色说明

  1. Element:元素接口,定义accept方法接收访问者。
  2. ​ConcreteElement:具体元素类(如ElementAElementB),实现accept方法。
  3. Visitor:访问者接口,为每个元素类声明visit方法。
  4. ConcreteVisitor:具体访问者,实现各元素的处理逻辑。
  5. Client:创建访问者并触发元素对访问者的接受。

3. 现代C++实现示例

场景:文档导出系统

​步骤1:定义元素与访问者接口
#include <iostream>  
#include <memory>  
#include <vector>  // 前向声明  
class TextElement;  
class ImageElement;  // 访问者接口  
class DocumentVisitor {  
public:  virtual ~DocumentVisitor() = default;  virtual void visit(const TextElement& text) = 0;  virtual void visit(const ImageElement& image) = 0;  
};  // 元素接口  
class DocumentElement {  
public:  virtual ~DocumentElement() = default;  virtual void accept(DocumentVisitor& visitor) const = 0;  
};  
步骤2:实现具体元素类
// 文本元素  
class TextElement : public DocumentElement {  
public:  TextElement(const std::string& content) : content_(content) {}  void accept(DocumentVisitor& visitor) const override {  visitor.visit(*this);  }  const std::string& getContent() const { return content_; }  private:  std::string content_;  
};  // 图片元素  
class ImageElement : public DocumentElement {  
public:  ImageElement(const std::string& path) : path_(path) {}  void accept(DocumentVisitor& visitor) const override {  visitor.visit(*this);  }  const std::string& getPath() const { return path_; }  private:  std::string path_;  
};  
步骤3:实现具体访问者(导出逻辑)​
// HTML导出访问者  
class HtmlExporter : public DocumentVisitor {  
public:  void visit(const TextElement& text) override {  html_ += "<p>" + text.getContent() + "</p>\n";  }  void visit(const ImageElement& image) override {  html_ += "<img src=\"" + image.getPath() + "\" />\n";  }  std::string getHtml() const { return html_; }  private:  std::string html_;  
};  // 纯文本导出访问者  
class TextExporter : public DocumentVisitor {  
public:  void visit(const TextElement& text) override {  text_ += text.getContent() + "\n";  }  void visit(const ImageElement& image) override {  text_ += "[图片: " + image.getPath() + "]\n";  }  std::string getText() const { return text_; }  private:  std::string text_;  
};  
步骤4:客户端代码
int main() {  // 创建文档元素  std::vector<std::unique_ptr<DocumentElement>> doc;  doc.push_back(std::make_unique<TextElement>("欢迎访问!"));  doc.push_back(std::make_unique<ImageElement>("photo.jpg"));  // 导出为HTML  HtmlExporter htmlExporter;  for (const auto& elem : doc) {  elem->accept(htmlExporter);  }  std::cout << "HTML导出结果:\n" << htmlExporter.getHtml() << "\n";  // 导出为纯文本  TextExporter textExporter;  for (const auto& elem : doc) {  elem->accept(textExporter);  }  std::cout << "文本导出结果:\n" << textExporter.getText() << "\n";  
}  /* 输出:  
HTML导出结果:  
<p>欢迎访问!</p>  
<img src="photo.jpg" />  文本导出结果:  
欢迎访问!  
[图片: photo.jpg]  
*/  

4. 应用场景示例

场景1:编译器符号表检查

class VariableNode;  
class FunctionNode;  class SymbolTableVisitor {  
public:  virtual void visit(const VariableNode& var) = 0;  virtual void visit(const FunctionNode& func) = 0;  
};  class VariableNode {  
public:  void accept(SymbolTableVisitor& visitor) { visitor.visit(*this); }  
};  class TypeChecker : public SymbolTableVisitor {  void visit(const VariableNode& var) override { /* 类型检查逻辑 */ }  void visit(const FunctionNode& func) override { /* 类型检查逻辑 */ }  
};  

场景2:3D模型渲染器

class Mesh;  
class Light;  class RenderVisitor {  
public:  virtual void render(const Mesh& mesh) = 0;  virtual void render(const Light& light) = 0;  
};  class OpenGLRenderer : public RenderVisitor {  void render(const Mesh& mesh) override { /* OpenGL绘制网格 */ }  void render(const Light& light) override { /* OpenGL处理光照 */ }  
};  

5. 优缺点分析

​优点​缺点
新增操作无需修改元素类新增元素类型需修改所有访问者接口
集中相关操作,提升内聚性破坏封装性,访问者可能访问私有成员
支持复杂结构遍历(如树形结构)增加系统复杂度,需维护访问者与元素关系

6. 调试与优化策略

调试技巧(VS2022)​

1. ​验证访问者分发逻辑:
  • accept()方法内设置断点,确认元素正确调用访问者的visit方法。
2. 类型安全检查:
  • 使用dynamic_cast检查访问者是否处理了所有元素类型:
void accept(DocumentVisitor& visitor) const {  if (auto* v = dynamic_cast<HtmlExporter*>(&visitor)) {  v->visit(*this);  } else {  throw std::runtime_error("不支持的访问者类型!");  }  
}  

性能优化

1. 访问者缓存:
  • 对频繁使用的访问者(如渲染器)进行实例复用,避免重复创建。
2. 并行访问:
  • 对独立元素使用多线程处理(需确保访问者线程安全):
#include <execution>  
std::for_each(std::execution::par, doc.begin(), doc.end(),  [&](auto& elem) { elem->accept(visitor); });  

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

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

相关文章

vue处理接口返回EventStream数据并进行展示

1、在 Vue 组件中连接外部 SSE 接口 HTML&#xff1a; <template><div class"ceshi-wrap"><h3 style"color:red;">来自本地文件的 SSE 流数据&#xff1a;</h3>-----<ul><li v-for"item in messages" :key&q…

Linux系统中切换CUDA版本的完整指南(含vim使用方法)

Linux系统中切换CUDA版本的完整指南&#xff08;含vim使用方法&#xff09; 在深度学习和高性能计算领域&#xff0c;经常需要在不同的CUDA版本之间切换&#xff0c;以满足不同项目的需求。本文将详细介绍如何在Linux系统中通过软链接切换CUDA版本的方法&#xff0c;并介绍了v…

批量压缩与优化 Excel 文档,减少 Excel 文档大小

当我们在 Excel 文档中插入图片资源的时候&#xff0c;如果我们插入的是原图&#xff0c;可能会导致 Excel 变得非常的大。这非常不利于我们传输或者共享。那么当我们的 Excel 文件非常大的时候&#xff0c;我们就需要对文档做一些压缩或者优化的处理。那有没有什么方法可以实现…

CentOS7安装DNS服务器bind

文章目录 安装DNS服务设置配置文件自定义域名解析完整配置 需求是公司内网服务器无法连接外网&#xff0c;需要在本地搭建DNS服务&#xff0c;这样物理机器迁移到内网后&#xff0c;通过域名解析访问服务 DNS服务器 172.25.14.215 ip域名172.25.14.216mysql.server172.25.14.2…

适合企业内训的AI工具实操培训教程(37页PPT)(文末有下载方式)

详细资料请看本解读文章的最后内容。 资料解读&#xff1a;适合企业内训的 AI 工具实操培训教程 在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术迅速发展&#xff0c;深度融入到各个领域&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;更是成…

leetcode0027 移除元素 - easy

1 题目&#xff1a;移除元素 27 官方标定难度&#xff1a;简单 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xf…

Mysql自增主键会遇到什么问题?

大家好&#xff0c;我是锋哥。今天分享关于【Mysql自增主键会遇到什么问题?】面试题。希望对大家有帮助&#xff1b; Mysql自增主键会遇到什么问题? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL自增主键&#xff08;AUTO_INCREMENT&#xff09;在使用过程…

linux 命令 case

在 Linux Shell 脚本中&#xff0c;case 是一个强大的多条件分支控制命令&#xff0c;用于基于模式匹配执行不同代码块。它类似于其他编程语言中的 switch-case 语句&#xff0c;但更灵活&#xff0c;支持通配符和模式组合。以下是其核心用法和实 一、基础语法 case 变量 in …

注意力机制,层归一化,RBA。KAN-ODE,小波KAN

目录 attention is all you need 翻译 多头注意力 8.6 Multi-head Self Attention 模型 模型架构 encoder安定 decode 注意力机制 位置编码 自注意力机制的优势 实验结果 结论 代码 Transformer 架构 代码实现思路 总结 编码器、解码器和位置编码的摆放顺序&…

思维训练让你更高、更强 |【逻辑思维能力】「刷题训练笔记」假设法模式逻辑训练题(1-5)

每日一刷 思维训练让你更高、更强&#xff01; 题目1 谁在说谎&#xff0c;谁拿走了零钱&#xff1f; 姐姐上街买菜回来后&#xff0c;就随手把手里的一些零钱放在了抽屉里&#xff0c;可是&#xff0c;等姐姐下午再去拿钱买菜的时候发现抽屉里的零钱没有了&#xff0c;于是&…

联想拯救者 M600 无线游戏鼠标|自定义驱动程序安装说明

安装步骤 下载后得到联想拯救者 M600 无线游戏鼠标自定义驱动程序“Setup_2.0.6.01271.exe”&#xff0c;右键 “ Setup_2.0.6.01271.exe ”&#xff0c;以管理员身份运行。 在安装向导窗口&#xff0c;点击“下一步” 在安装向导“许可协议”窗口&#xff0c;勾选“我接受协议…

Deep Image Deblurring: A Survey 去模糊文献阅读

深度图像去模糊&#xff1a;综述 摘要 图像去模糊是低层计算机视觉中的经典问题&#xff0c;其目标是从模糊的输入图像中恢复出清晰图像。模糊可能由多种因素引起&#xff0c;例如失焦、相机抖动或目标快速运动。近年来&#xff0c;深度学习技术的进步显著推动了这一问题的解…

Python多版本环境管理UV

Python多版本环境管理UV 1-参考网址 Python虚拟环境UV管理工具-官网Python虚拟环境UV管理工具-快速开始pyproject.toml使用指导 2-核心知识点 1&#xff09;python项目维护requirements.txt2&#xff09;python机器学习环境Anaconda3&#xff09;python轻量级环境管理uv4&…

如何快速检测光模块内部光纤裂纹?

关键词&#xff1a;光纤裂纹、白光干涉、光纤微裂纹检测仪 概述&#xff1a; 随着大数据时代对数据量需求的爆炸式增长&#xff0c;光通信系统也在不断的更新升级。光通信产业链上的光收发模块作为核心组件之一&#xff0c;其性能优劣直接影响系统的通信质量。由于光模块速率…

PyQt基础——简单的窗口化界面搭建以及槽函数跳转

一、代码实现 import sysfrom PyQt6.QtGui import QPixmap from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QLineEdit, QMessageBox from PyQt6.uic import loadUi from PyQt6.QtCore import Qtclass LoginWindow(QWidget):def __init__(self):sup…

深入理解 ALSA 声卡驱动:从理论到实践,解决嵌入式 Linux 声卡无声问题

&#x1f4cc; 1. 引言 在嵌入式 Linux 设备上&#xff0c;ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;是音频驱动的核心框架。 然而&#xff0c;在实际部署过程中&#xff0c;我们可能会遇到 声卡无声、通道不匹配、I2S 传输异常等问题。 本文将深入解析…

Windows远程桌面黑屏怎么办?

在使用Windows远程桌面连接另一台电脑时&#xff0c;用户经常会遇到Windows远程桌面黑屏的问题。那么&#xff0c;该如何有效地解决Windows远程桌面黑屏的问题呢&#xff1f;遇到远程桌面连接黑屏的问题时&#xff0c;可以通过在本地组策略编辑器中禁用WDDM图形显示驱动来解决。…

【VSCODE 插件 可视化】:SVG 编辑插件 SVG Editor

插件下载 svgeditor 创建文件 Windows/Linux 快捷键 Ctrl Shift P 打开VSCODE 命令面板查找 New File With Svg Editor 编辑文件 保存文件 打开文件以继续编辑 CG 选中多个&#xff1a;shift单击没找到横向分布功能无法用键盘微调位置

python3GUI--模仿安卓桌面 By:PyQt5(附下载地址)

文章目录 一&#xff0e;前言二&#xff0e;展示1.主界面2.设置页面3.更换了壁纸且切换桌面页面 三&#xff0e;项目分享1.项目代码结构2.组件代码分享 四&#xff0e;总结 文件大小25.5M&#xff0c;欢迎下载体验&#xff01;点击下载 一&#xff0e;前言 今天给大家推荐我用…

stm32 L432KC(mbed)入门第一课

目录 一. 前言 二. 专栏意义 三. MS入门第一课 一. 前言 新的一年MS课程又开始了&#xff0c;同时也到了该专栏的第三个年头。在前两年中&#xff0c;该专栏帮助了很多第一次接触单片机的同学。其中&#xff0c;有的同学订阅专栏是为了更好的完成并且通过MS这门课程&#xf…