C++设计模式_24_Visitor 访问器

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 dispatchElementA 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添加Visitor2ElementA 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.rhkb.cn/news/176760.html

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

相关文章

HR怎么看待PMP证书呢?

作为之前在hr面试过的我来说&#xff0c;这些证书什么的还是很重要的&#xff0c;当你选择一个适合自己的职位时候&#xff0c;大多都是需要看到你的专业能力来进行判断你薪资和适合岗位的依据&#xff0c;pmp证书就是一个很好的衡量证据&#xff0c;而且这个含金量不用了说了非…

centos7 配置搭建 wordpress 博客

环境配置 系统:centos7 CPU:2核 内存:4G 硬盘:40G 一、登录云服务器器 1.单击实例--实例名称 2. 选择安全组页签,单击安全组操作列的管理规则, 3.在入方向添加需要放行的端口。本教程中,在安全组入方向放行SSH默认22端口、Apache默认80端口 4.登录服务器 5.更改主…

读取谷歌地球的kml文件中的经纬度坐标

最近我在B站上传了如何获取研究边界的视频&#xff0c;下面分享一个可以读取kml中经纬度的matlab函数&#xff0c;如此一来就可以获取任意区域的经纬度坐标了。 1.谷歌地球中划分区域 2.matlab读取kml文件 function [sname,lon,lat] kml2xy(ip_kml) % ip_kml ocean_distubu…

高级文本编辑软件 UltraEdit mac中文版介绍说明

UltraEdit mac是一款在Windows系统中非常出名的文本编辑器&#xff0c; UltraEdit for mac对于IT程序猿来说&#xff0c;更是必不可少&#xff0c;可以使用UltraEdit编辑配置文件、查看16进制文件、代码高亮显示等&#xff0c;虽然Mac上已经有了很多优秀的文本编辑器&#xff0…

Mybatis延迟加载(缓存)

延迟加载 分步查询的优点&#xff1a;可以实现延迟加载&#xff0c;但是必须在核心配置文件中设置全局配置信息&#xff1a;lazyLoadingEnabled&#xff1a;延迟加载的全局开关。当开启时&#xff0c;所有关联对象都会延迟加载 aggressiveLazyLoading&#xff1a;当开启时&…

快速了解ClickHouse!

简介 ClickHouse是一个开源列式数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;用于在线分析处理&#xff08;OLAP&#xff09;&#xff1a; 列式存储&#xff1a;与传统的行式数据库不同&#xff0c;ClickHouse以列的形式存储数据&#xff0c;这使得在分析大量数据时…

微信小程序如何利用接口返回经纬计算实际位置并且进行导航功能【下】

如果要在微信小程序内部导航的话可以使用wx.navigateToMiniProgram方法来打开腾讯地图小程序&#xff0c;并传递目的地的经纬度信息。 目录 1.如何获取高精地址 2.如何调起地图 3.实现效果 navigateToDestination: function() {let that this;var latitude parseFloa…

Kubernetes 概述以及Kubernetes 集群架构与组件

目录 Kubernetes概述 K8S 是什么 为什么要用 K8S K8S 的特性 Kubernetes 集群架构与组件 核心组件 Master 组件 Node 组件 ​编辑 Kubernetes 核心概念 常见的K8S按照部署方式 Kubernetes概述 K8S 是什么 K8S 的全称为 Kubernetes,Kubernetes 是一个可移植、可扩…

记一次有趣的tp5代码执行

目录 0x00 前言 0x01 基础信息 0x02 突破 base64编码与php://filter伪协议 tp 5 method代码执行的细节 0x03 总结 免费领取安全学习资料包&#xff01;&#xff08;私聊进群一起学习&#xff0c;共同进步&#xff09;​编辑 0x00 前言 朋友之前给了个站&#xff0c;拿了…

黑马 小兔鲜儿 uniapp 小程序开发- 微信登录用户模块- 06-07

黑马 小兔鲜儿 uniapp 小程序开发- 商品详情模块- day05-CSDN博客 小兔鲜儿 - 微信登录-06 涉及知识点&#xff1a;微信授权登录&#xff0c;文件上传&#xff0c;Store 状态管理等。 微信登录 微信小程序的开放能力&#xff0c;允许开发者获取微信用户的基本信息&#xff…

HBase理论与实践-基操与实践

基操 启动&#xff1a; ./bin/start-hbase.sh 连接 ./bin/hbase shell help命令 输入 help 然后 <RETURN> 可以看到一列shell命令。这里的帮助很详细&#xff0c;要注意的是表名&#xff0c;行和列需要加引号。 建表&#xff0c;查看表&#xff0c;插入数据&#…

Python Django 之全局配置 settings 详解

文章目录 1 概述1.1 Django 目录结构 2 常用配置&#xff1a;settings.py2.1 注册 APP&#xff1a;INSTALLED_APPS2.2 模板路径&#xff1a;TEMPLATES2.3 静态文件&#xff1a;STATICFILES_DIRS2.4 数据库&#xff1a;DATABASES2.5 允许访问的主机&#xff1a;ALLOWED_HOSTS 1 …

arcgis将合并(组合)要素拆分的方法

1、打开一幅图&#xff0c;发现两块区域被连接成一块区域&#xff0c;如下&#xff1a; 2、在可编辑状态下&#xff0c;进行拆分&#xff0c;先选中待拆分要素&#xff0c;如下&#xff1a; 3、拆分后&#xff0c;如下&#xff1a;

CSS3弹性布局

2009年&#xff0c;W3C提出一种崭新的布局方案—弹性盒(Flexbox)布局&#xff0c;使用该模型可以轻松地创建自适应窗口的流动布局&#xff0c;或者自适应字体大小的弹性布局。W3C的弹性盒布局分为旧版本、新版本及混合过渡版本3种不同的设计方案&#xff0c;其中混合过渡版本主…

idea 拉取代码

md老长时间 不用git 差点忘了 现在 演示 非常简单

【计算机网络】分层模型和应用协议

网络分层模型和应用协议 1. 分层模型 1.1 五层网络模型 网络要解决的问题是&#xff1a;两个程序之间如何交换数据。 四层&#xff1f;五层&#xff1f;七层&#xff1f; 2. 应用层协议 2.1 URL URL&#xff08;uniform resource locator&#xff0c;统一资源定位符&#…

基于深度学习的安全帽识别检测系统(python OpenCV yolov5)

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、研究的内容与方法二、基于深度学习的安全帽识别算法2.1 深度学习2.2 算法流程2.3 目标检测算法2.3.1 Faster R-CNN2.3.2 SSD2.3.3 YOLO v3 三 实验与结果分析3.1 实验数据集3.1.1 实验数据集的构建3.1.2 数据…

基于松鼠算法的无人机航迹规划-附代码

基于松鼠算法的无人机航迹规划 文章目录 基于松鼠算法的无人机航迹规划1.松鼠搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用松鼠算法来优化无人机航迹规划。 1.松鼠搜索算法 …

安装pytorch报错torch.cuda.is_available()=false的解决方法

参考文章&#xff1a; https://blog.csdn.net/qq_46126258/article/details/112708781 https://blog.csdn.net/Andy_Luke/article/details/122503884 https://blog.csdn.net/anmin8888/article/details/127910084 https://blog.csdn.net/zcs2632008/article/details/127025294 …

Linux内核是如何创建进程?

目录 1.Linux如何创建进程 2.fork函数原理 2.1 fork函数原型 2.2 fork函数实现原理 2.3 父子进程虚拟地址空间&#xff08;mm_struct&#xff09;之间的关系 2.4 写时拷贝&#xff08;copy-on-write&#xff09;技术 2.5 父子进程如何共享文件&#xff08;files_struct&…