C++设计模式_24_Visitor 访问器

news2025/7/11 3:45:37

Visitor 访问器也是属于“行为变化”模式。

文章目录

  • 1. 动机( Motivation)
  • 2. 代码演示Visitor 访问器
  • 3. 模式定义
  • 4. 结构(Structure)
  • 5. 要点总结
  • 6. 其他参考

1. 动机( Motivation)

  • 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。

比如以下为类层次结构:Element为基类,ElementA 、ElementB是子类

#include <iostream>
using namespace std;

class Visitor;


class Element
{
public:
    virtual void Func1() = 0;
    
    virtual ~Element(){}
};

class ElementA : public Element
{
public:
    void Func1() override{
        //...
    }    
    
};

class ElementB : public Element
{
public:
    void Func1() override{
        //***
    }
    
};

Func1()为已经有的行为,假如需求有变更,需要添加新的行为,大家直观的操作肯定是直接在基类中加新的行为例如Func2(),在子类中override对应的行为。

#include <iostream>
using namespace std;

class Visitor;


class Element
{
public:
    virtual void Func1() = 0;
    
    virtual void Func2(int data)=0;
    virtual void Func3(int data)=0;
    //...
    
    virtual ~Element(){}
};

class ElementA : public Element
{
public:
    void Func1() override{
        //...
    }
    
    void Func2(int data) override{
        //...
    }
    
};

class ElementB : public Element
{
public:
    void Func1() override{
        //***
    }
    
    void Func2(int data) override {
        //***
    }
    
};

上面的操作是很常见的操作,但这不一个类的设计过程,我们是讲当你已经完成了代码,已经部署分发了又想添加新的操作,这样改来代价就是很高的,因为你改的是基类,我们有一个前提说你要在一个基类里去添加针对这个对象结构的所有的行为,每一个子类都要重写的版本,上面的这种写法是违背了设计原则的:开闭原则。遇到这种情况怎么办呢?

  • 如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

2. 代码演示Visitor 访问器

为了解决上面存在的问题,我们来看Vistor模式是如何解决的呢?

整体代码:

#include <iostream>
using namespace std;

class Visitor;


class Element
{
public:
    virtual void accept(Visitor& visitor) = 0; //第一次多态辨析

    virtual ~Element(){}
};

class ElementA : public Element
{
public:
    void accept(Visitor &visitor) override {
        visitor.visitElementA(*this);
    }
    

};

class ElementB : public Element
{
public:
    void accept(Visitor &visitor) override {
        visitor.visitElementB(*this); //第二次多态辨析
    }

};


class Visitor{
public:
    virtual void visitElementA(ElementA& element) = 0;
    virtual void visitElementB(ElementB& element) = 0;
    
    virtual ~Visitor(){}
};

//==================================

//扩展1
class Visitor1 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor1 is processing ElementA" << endl;
    }
        
    void visitElementB(ElementB& element) override{
        cout << "Visitor1 is processing ElementB" << endl;
    }
};
     
//扩展2
class Visitor2 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor2 is processing ElementA" << endl;
    }
    
    void visitElementB(ElementB& element) override{
        cout << "Visitor2 is processing ElementB" << endl;
    }
};
        
    

        
int main()
{
    Visitor2 visitor;
    ElementB elementB;
    elementB.accept(visitor);// double dispatch
    
    ElementA elementA;
    elementA.accept(visitor);

    
    return 0;
}

代码分析:

Vistor模式有一个前提就是我能预料到未来可能会给整个类层次结构增加新的操作,但是不知道要加什么和多少的操作,这个时候就需要进行预先的设计

class Element
{
public:
    virtual void accept(Visitor& visitor) = 0; //第一次多态辨析

    virtual ~Element(){}
};

virtual void accept(Visitor& visitor) = 0; //第一次多态辨析函数,参数为Visitor类对象,在Visitor类中针对每一个子类写一个虚函数

class Visitor{
public:
    virtual void visitElementA(ElementA& element) = 0;
    virtual void visitElementB(ElementB& element) = 0;
    
    virtual ~Visitor(){}
};

ElementA继承自Element,然后重写accept()函数,接受Visitor作为参数,调用visitElementA(),传入的为this,也就是ElementA。这样的写法只是预先的设计了将来可能会增加新的操作

class ElementA : public Element
{
public:
    void accept(Visitor &visitor) override {
        visitor.visitElementA(*this);
    }
    

};

ElementB也是一样的操作

在整个代码中//==================================之上是已经做好的设计,之下就是将来,现在我又新的需求了,我需要给前面整个类层次结构增加新的操作,怎么做呢?

class Visitor1继承Visitor,在子类中实现visitElementA和visitElementB的虚函数,

//扩展1
class Visitor1 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor1 is processing ElementA" << endl;
    }
        
    void visitElementB(ElementB& element) override{
        cout << "Visitor1 is processing ElementB" << endl;
    }
};

将来有新的需求,就增加Visitor2,去继承Visitor。

//扩展2
class Visitor2 : public Visitor{
public:
    void visitElementA(ElementA& element) override{
        cout << "Visitor2 is processing ElementA" << endl;
    }
    
    void visitElementB(ElementB& element) override{
        cout << "Visitor2 is processing ElementB" << endl;
    }
};

假如说我需要添加Visitor2的操作,Visitor2 visitor;先创建Visitor2的对象,然后ElementB elementB;创建ElementB对象,需要给ElementB添加操作elementB.accept(visitor);,这个调用是Visitor 模式中最重要的需要理解了。

首先elementB.accept(visitor);class ElementB : public Element去调用,visitor.visitElementB(*this);,就会找到class Visitor2 : public Visitor中的void visitElementB(ElementB& element) override,那么执行的命令也就是:cout << "Visitor2 is processing ElementB" << endl;,也就是我们想给ElementB添加Visitor2操作

int main()
{
    Visitor2 visitor;
    ElementB elementB;
    elementB.accept(visitor);// double dispatch
    
    //给ElementA添加Visitor2
    ElementA elementA;
    elementA.accept(visitor);

    
    return 0;
}

所以Visitor模式中有一个double dispatch(二次多态辨析),哪两次呢?

第一次是调用accept()虚函数时候;第二次是visitor.visitElementB

3. 模式定义

表示一个作用于某对象结构(Element)中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。

----《设计模式》GoF

class Visitor1 : public Visitorclass Visitor2 : public Visitor都是新的操作,每一次添加一次新操作都是扩展,这就是开闭原则的体现–利用子类的方式实现新的操作。

4. 结构(Structure)

在这里插入图片描述

上图是《设计模式》GoF中定义的Visitor 访问器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。

在这里插入图片描述

这个设计模式很多人会有感受就是它有一些缺点,这个模式不是随便就能用的。上图中VistorElement是需要稳定的,但是这两个稳定是不够的。因为Vistor稳定,里面需要ConcreteElementA和ConcreteElementB,实际上也就需要ConcreteElementA和ConcreteElementB都必须是稳定。因为当你写Vistor的时候,必须知道Element有几个子类,所以Vistor写下来的前提是Element类的子类个数能确定,这是一个非常大的前提,很容易保证不到。

假如我要设计一个图像系统,其中有shape基类,rectangle子类,circle子类等,但Vistor要求这些子类必须稳定,这个条件常常满足不了,如果没满足,Element中新加一个子类,假如是ElementC,那么Vistor基类也需要跟着改变,又打破了开闭原则

所以Vistor基类的稳定的前提是Element所有的子类全确定,这是这个模式非常大的缺点,我们也需要说一下,蓝色的部分是可以扩展式的变化,将来需要添加新的操作的时候,就去继承Vistor实现Vistor新的子类,比如说Vistor3,针对它里面所有的Element去写所有的函数。

坦白来说,这个模式要求的稳定就带来了极大的脆弱性,如果Element类层次结构不能稳定,Vistor就不能使用了,这个条件十分苛刻

5. 要点总结

  • Visitor模式通过所谓双重分发(double dispatch)来实现在不更改(不添加新的操作-编译时时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。

不在Element上添加新的源代码;//==================================以下为运行时…

  • 所谓双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visitElementX方法的多态辨析。

  • Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类)会导致Vistor类的改变。因此Vistor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。

6. 其他参考

C++设计模式——访问者模式

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Techlink TL24G10 网络变压器 10G 基座单端口变压器

功能特征&#xff1a; 1、符合IEEE 802.3标准。 2、符合RoHS。 3、工作温度范围&#xff1a;0C至70C。 4、储存温度范围&#xff1a;-20C至125C。 封装&#xff1a;SOP24

横屏签字板手写签名并旋转90°转为横屏显示base64

手写签名并旋转90转为横屏显示base64 base64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAApsAAAF3CAYAAADq/IAAAAAAAXNSR0IArs4c6QAAIABJREFUeF7t3W3MPlldH/CvwLK7wEKNoiABZLvYiBqIdJGnQlor9gVBaAtJmzQsoI2wgKD4hqVNmrD7ohWhPFRSIgaKO0XTS8AVLb4AIVtFnaSiMPSktxoyVVWFxAF…

ios开发者(Apple Developer Program)如何续费

前言 会员资格到期之日前 30 天才可以进行续费 1. 联系官方 查询续费方式与入口 官网地址&#xff1a;https://developer.apple.com/ 2. Apple Developer App 进行续订 需要下载 Apple Developer 进入App内进行续订。 3. 如果没有续订按钮&#xff0c;联系官方更换订阅…

RTSP/Onvif安防视频平台EasyNVR接入EasyNVS,出现Login error报错的解决方法

安防视频监控汇聚EasyNVR平台&#xff0c;是基于RTSP/Onvif协议的安防视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xff0c;我们也提…

厦门万宾科技智能井盖监测仪器的作用如何?

越来越多的人们希望改善生活&#xff0c;走出农村走出大山&#xff0c;前往城市之中居住。由此城市的人口和车辆在不断增加&#xff0c;与之而来的是城市的交通压力越来越大&#xff0c;时常会出现道路安全隐患&#xff0c;这给城市未来发展和智慧城市建设都带来一定的难题&…

vue详细安装教程

这里写目录标题 一、下载和安装node二、创建全局安装目录和缓存日志目录三、安装vue四、创建一个应用程序五、3x版本创建六、创建一个案例 一、下载和安装node 官网下载地址&#xff1a;https://nodejs.org/en/download 选择适合自己的版本&#xff0c;推荐LTS&#xff0c;长久…

模块化机柜PDU为数据中心机房末端配电提供可靠解决方案

数据中心是国家确定的“新基建”七大领域之一&#xff0c;数据中心在国民经济和社会发展中所起的作用越来越重要&#xff0c;数据中心已经成为了各行各业的关键基础设施&#xff0c;数据中心供配电系统相当于一个人的“心脏和血管”&#xff0c;负责把能量输送到系统的每一台设…

驱动开发11-1 编写IIC驱动-读取温湿度数据

头文件 head.h #ifndef __HEAD_H__ #define __HEAD_H__ #define GET_HUM _IOR(m, 1, int) #define GET_TEM _IOR(m, 0, int) #endif 应用程序 si7006.c #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #inc…

人人都能看懂的DDPM反向降噪过程公式推导

0 前言 上一篇介绍了前向加噪过程&#xff0c;得到如下从 x 0 x_0 x0​ 一步到 x t x_t xt​ 过程&#xff1a; α t β t 1 \alpha_t \beta_t1 αt​βt​1&#xff0c;其中 β t \beta_t βt​ 是正态分布方差&#xff0c;即第 t t t 步产生的噪声从 N ( 0 , β t ) …

伽马函数 简要总结

1、定义公式&#xff1a; &#xff08;上面一个△ 意为“定义为”&#xff09; 例1&#xff1a; 例2&#xff1a; 2、性质 3、举例 例1&#xff1a; 例2&#xff1a; 例3&#xff1a; 笔记记录时间&#xff1a;2023.11.01&#xff0c;笔记记录自汤老师讲解的伽马函数内容。…

算法与数据结构-回溯算法

文章目录 如何理解“回溯算法”&#xff1f;两个回溯算法的经典应用0-1 背包正则表达式 如何理解“回溯算法”&#xff1f; 笼统地讲&#xff0c;回溯算法很多时候都应用在“搜索”这类问题上。不过这里说的搜索&#xff0c;并不是狭义的指我们前面讲过的图的搜索算法&#xf…

损失函数总结(十一):Huber Loss、SmoothL1Loss

损失函数总结&#xff08;十一&#xff09;&#xff1a;Huber Loss、SmoothL1Loss 1 引言2 损失函数2.1 Huber Loss2.2 SmoothL1Loss 3 总结 1 引言 在前面的文章中已经介绍了介绍了一系列损失函数 (L1Loss、MSELoss、BCELoss、CrossEntropyLoss、NLLLoss、CTCLoss、PoissonNL…

c语言基础:L1-067 洛希极限

科幻电影《流浪地球》中一个重要的情节是地球距离木星太近时&#xff0c;大气开始被木星吸走&#xff0c;而随着不断接近地木“刚体洛希极限”&#xff0c;地球面临被彻底撕碎的危险。但实际上&#xff0c;这个计算是错误的。 洛希极限&#xff08;Roche limit&#xff09;是一…

01-开发第一个Vue程序,了解Vue构造函数的配置项data,template,插值语法,el

Vue的快速入门 下载并安装vue.js Vue是一个基于JavaScript实现的框架, 要使用它就需要从Vue官网下载 vue.js文件 第一步&#xff1a;打开Vue2官网&#xff0c;点击下图所示的起步 第二步&#xff1a;继续点击下图所示的安装 第三步&#xff1a;在安装页面向下滚动&#xff0…

永恒之蓝(MS17-010)漏洞利用

永恒之蓝&#xff08;eternalblue) 永恒之蓝&#xff08;Eternal Blue&#xff09;爆发于2017年4月14日晚&#xff0c;是一种利用Windows系统的SMB协议漏洞来获取系统的最高权限&#xff0c;以此来控制被入侵的计算机。甚至于2017年5月12日&#xff0c; 不法分子通过改造“永恒…

k8s约束调度

Kubernetes 是通过 List-Watch **** 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 list-watch核心组件为Controller Manager、Scheduler 和 kubelet&#xff0c;这三者协助完成了pod节点的建立过程。 在 Kubernetes 中&…

89 柱状图中最大的矩形

柱状图中最大的矩形 类似接雨水&#xff08;反过来&#xff0c;相当于找接雨水最少的一段&#xff09;题解1 暴力搜索&#xff08;超时&#xff09; O ( N 2 ) O(N^2) O(N2)另一种 题解2 单调栈【重点学习】常数优化 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的…

【接口测试】目前市面上流行的接口大多有哪几种协议的接口?

首先&#xff0c;关于协议这个词&#xff0c;你要清楚。 接口测试它是基于什么进行测试的&#xff0c;接口测试是什么测试类型&#xff0c;其实有有时候问到一个问题&#xff0c;关联性很强。很多关联性的问题你都可以去考虑。 首先接口测试一个功能黑盒测试&后端&#x…

Snackbar使用介绍及自定义

Snackbar使用介绍及自定义 前言一、Snackbar是什么&#xff1f;二、简单使用三、进阶使用参考 [Android 快别用Toast了&#xff0c;来试试Snackbar](https://blog.csdn.net/g984160547/article/details/121269520) 总结 前言 有个UI要显示自定义样式的toast&#xff0c;并居中…

APISpace 天气预报查询API接口案例代码

1.天气预报查询API产品介绍 APISpace 的 天气预报查询&#xff0c;支持全国以及全球多个城市的天气查询&#xff0c;包含国内3400个城市以及国际4万个城市的实况数据&#xff0c;同时也支持全球任意经纬度查询&#xff0c;接口会返回该经纬度最近的站点信息&#xff1b;更新频率…