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 由多个汽车元素组成。因此,Caraccept 成员函数将接受调用委托给它的所有汽车零部件。

     关于访问者的一个关键特征是:它依赖于两个对象,决定执行什么操作:访问者和被访问的对象。

五:代码示例

#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

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

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

相关文章

DeepSeek在FPGA/IC开发中的创新应用与未来潜力

随着人工智能技术的飞速发展&#xff0c;以DeepSeek为代表的大语言模型&#xff08;LLM&#xff09;正在逐步渗透到传统硬件开发领域。在FPGA&#xff08;现场可编程门阵列&#xff09;和IC&#xff08;集成电路&#xff09;开发这一技术密集型行业中&#xff0c;DeepSeek凭借其…

ZU47DR 100G光纤 高性能板卡

简介 2347DR是一款最大可提供8路ADC接收和8路DAC发射通道的高性能板卡。板卡选用高性价比的Xilinx的Zynq UltraScale RFSoC系列中XCZU47DR-FFVE1156作为处理芯片&#xff08;管脚可以兼容XCZU48DR-FFVE1156&#xff0c;主要差别在有无FEC&#xff08;信道纠错编解码&#xff0…

详解SQLAlchemy的函数relationship

在 SQLAlchemy 中&#xff0c;relationship 是一个非常重要的函数&#xff0c;用于定义模型之间的关系。它用于在 ORM 层面上表示数据库表之间的关联关系&#xff08;如 1 对 1、1 对多和多对多&#xff09;。relationship 的主要作用是提供一个高级接口&#xff0c;用于在模型…

【Matlab优化算法-第14期】基于智能优化算法的VMD信号去噪项目实践

基于智能优化算法的VMD信号去噪项目实践 一、前言 在信号处理领域&#xff0c;噪声去除是一个关键问题&#xff0c;尤其是在处理含有高斯白噪声的复杂信号时。变分模态分解&#xff08;VMD&#xff09;作为一种新兴的信号分解方法&#xff0c;因其能够自适应地分解信号而受到…

C++ 继承(1)

1.继承概念 我们平时有时候在写多个有内容重复的类的时候会很麻烦 比如我要写Student Teacher Staff 这三个类 里面都要包含 sex name age成员变量 唯一不同的可能有一个成员变量 但是这三个成员变量我要写三遍 太麻烦了 有没有好的方式呢&#xff1f; 有的 就是继承…

生成式聊天机器人 -- 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 -- 下

生成式聊天机器人 -- 基于Pytorch Global Attention 双向 GRU 实现的SeqToSeq模型 -- 下 训练Masked 损失单次训练过程迭代训练过程 测试贪心解码(Greedy decoding)算法实现对话函数 训练和测试模型完整代码 生成式聊天机器人 – 基于Pytorch Global Attention 双向 GRU 实…

《ARM64体系结构编程与实践》学习笔记(四)

MMU内存管理 1.MMU内存管理&#xff08;armv8.6手册的D5章节&#xff09;&#xff0c;MMU包含快表TLB&#xff0c;TLB是对页表的部分缓存&#xff0c;页表是存放在内存里面的。 AArch64仅仅支持Long Descriptor的页表格式&#xff0c;AArch32支持两种页表格式Armv7-A Short De…

如何在Vscode中接入Deepseek

一、获取Deepseek APIKEY 首先&#xff0c;登录Deepseek官网的开放平台&#xff1a;DeepSeek 选择API开放平台&#xff0c;然后登录Deepseek后台。 点击左侧菜单栏“API keys”&#xff0c;并创建API key。 需要注意的是&#xff0c;生成API key复制保存到本地&#xff0c;丢失…

Docker 部署 MinIO | 国内阿里镜像

一、导读 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;基于Apache License v2.0开源协议&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用&#xff0c;例如 NodeJS、Redis、MySQL等。 二、…

DeepSeek-R1 32B Windows+docker本地部署

最近国产大模型DeepSeek兴起&#xff0c;本地部署了一套deepseek同时集成Open WebUI界面,给大家出一期教程。 软件&#xff1a;Ollama、docker、Open WebUI 一、用Ollama下载模型 首先我们需要安装Ollama&#xff0c;它可以在本地运行和管理大模型。 到Ollama官网 https://ol…

TCP服务器与客户端搭建

一、思维导图 二、给代码添加链表 【server.c】 #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <fcntl.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.…

python爬虫--简单登录

1&#xff0c;使用flask框架搭建一个简易网站 后端代码app.py from flask import Flask, render_template, request, redirect, url_for, sessionapp Flask(__name__) app.secret_key 123456789 # 用于加密会话数据# 模拟用户数据库 users {user1: {password: password1}…

ESXi Host Client创建ubuntu虚拟机教程及NVIDIA显卡驱动安装

参考文章 VMware虚拟机显卡直通记录 AIGC 实战&#xff08;环境篇&#xff09; - EXSI 8.0 Debian安装RTX3060显卡驱动 重点介绍 client版本是7.0.3 注意&#xff1a;下图中不要选择BIOS 按照两个链接中的方法进行操作&#xff0c;以及本章节的上面几个图片的配置之后&a…

Maven入门核心知识点总结

Maven 1. POM&#xff08;Project Object Model&#xff09;2. 坐标&#xff08;Coordinates&#xff09;3. 依赖管理&#xff08;Dependency Management&#xff09;4. 常用五个生命周期&#xff08;Life Circle&#xff09;5. Maven 仓库&#xff08;Maven Repository&#x…

测试中的第一性原理:回归本质的质量思维革命

在软件工程领域&#xff0c;测试活动常被惯性思维和经验主义所主导——测试用例库无限膨胀、自动化脚本维护成本居高不下、测试策略与业务目标渐行渐远。要突破这种困境&#xff0c;第一性原理&#xff08;First Principles Thinking&#xff09;提供了独特的解题视角&#xff…

Rust语言进阶之标准输入: stdin用法实例(一百零五)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

SpringBoot速成(七)注册实战P2-P4

1.创建 数据库创建 依赖引入 <!-- mybatis起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency> <…

Spring Boot接入Deep Seek的API

1&#xff0c;首先进入deepseek的官网&#xff1a;DeepSeek | 深度求索&#xff0c;单击右上角的API开放平台。 2&#xff0c;单击API keys&#xff0c;创建一个API&#xff0c;创建完成务必复制&#xff01;&#xff01;不然关掉之后会看不看api key&#xff01;&#xff01;&…

【C++学习篇】C++11第二期学习

目录 1. 可变参数模板 1.1 基本语法及原理 1.2 包扩展 1.3empalce系列接⼝ 2. lamba 2.1 lambda的语法表达式 2.2 捕捉列表 2.3 lamba的原理 1. 可变参数模板 1.1 基本语法及原理 1. C11⽀持可变参数模板&#xff0c;也就是说⽀持可变数量参数的函数模板和类模板&…

开放式TCP/IP通信

一、1200和1200之间的开放式TCP/IP通讯 第一步&#xff1a;组态1214CPU&#xff0c;勾选时钟存储器 第二步&#xff1a;防护与安全里面连接机制勾选允许PUT/GET访问 第三步&#xff1a;添加PLC 第四步&#xff1a;点击网络试图&#xff0c;选中网口&#xff0c;把两个PLC连接起…