【设计模式深度剖析】【9】【行为型】【访问者模式】| 以博物馆的导览员为例加深理解
👈️上一篇:备忘录模式 | 下一篇:状态模式👉️
设计模式-专栏👈️
文章目录
- 访问者模式
- 定义
- 英文原话
- 直译
- 如何理解呢?
- 访问者模式的角色
- 类图
- 代码示例
- 访问者模式的应用
- 优点
- 缺点
- 使用场景
- 示例解析:博物馆的导览员
- 代码示例
访问者模式
访问者模式(Visitor Pattern)就像一位多才多艺的导游,能够根据不同的景点(数据结构中的元素)提供个性化的解说服务(操作),而无需改变景点的本质。
定义
英文原话
The Visitor pattern is a behavioral design pattern that allows you to add new operations to objects without changing their classes. A visitor is a class with methods that correspond to the classes of an object structure it visits. A client uses the visitor to perform the operations on the elements of the object structure.
直译
访问者模式是一种行为设计模式,它允许我们在不改变对象类的情况下为对象添加新的操作。访问者是一个类,它包含的方法对应于它所访问的对象结构中的类。客户端使用访问者来对对象结构的元素执行操作。
如何理解呢?
想象一下我们有一个装满各种形状(圆形、矩形、三角形)的盒子。现在我们想对这些形状进行不同的操作,比如测量它们的面积或周长。使用访问者模式,我们可以创建一个“测量工具”(即访问者),它知道如何与每种形状交互并获取所需的信息,而不需要改变形状本身。
访问者模式的角色
访问者模式中的角色包括:
- Visitor(访问者):这是一个接口或抽象类,声明了访问特定元素类时需要执行的操作。
- ConcreteVisitor(具体访问者):实现了Visitor接口或继承了Visitor抽象类,实现了对元素类中每个元素的访问操作。
- Element(元素):这是一个接口或抽象类,声明了一个接受访问者对象的方法(通常是
accept)。 - ConcreteElement(具体元素):实现了Element接口或继承了Element抽象类,并实现
accept方法以接受访问者的访问。 - ObjectStructure(对象结构):能够枚举它的元素,并可以提供一个高层接口以允许访问者访问它的元素。这通常是一个集合类,如列表或树。
类图

代码示例
package com.polaris.designpattern.list3.behavioral.pattern09.visitor.classicdemo;import java.util.ArrayList;
import java.util.List;// Visitor 接口
interface Visitor {void visit(ConcreteElementA element);void visit(ConcreteElementB element);
}// ConcreteVisitor 类
class ConcreteVisitor implements Visitor {@Overridepublic void visit(ConcreteElementA element) {System.out.println("Visiting ConcreteElementA: " + element.operationA());}@Overridepublic void visit(ConcreteElementB element) {System.out.println("Visiting ConcreteElementB: " + element.operationB());}
}// Element 接口
interface Element {void accept(Visitor visitor);
}// ConcreteElementA 类
class ConcreteElementA implements Element {@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String operationA() {return "elementA info...";}
}// ConcreteElementB 类
class ConcreteElementB implements Element {@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}public String operationB() {return "elementB info...";}
}// ObjectStructure 类(这里简单用ArrayList作为示例)class ObjectStructure {private List<Element> elements = new ArrayList<>();public void add(Element element) {elements.add(element);}public void accept(Visitor visitor) {for (Element element : elements) {element.accept(visitor);}}
}// 客户端代码
public class VisitorPatternDemo {public static void main(String[] args) {ObjectStructure objectStructure = new ObjectStructure();objectStructure.add(new ConcreteElementA());objectStructure.add(new ConcreteElementB());Visitor visitor = new ConcreteVisitor();objectStructure.accept(visitor);}
}
/* Output:
Visiting ConcreteElementA: elementA info...
Visiting ConcreteElementB: elementB info...
*///~
在这个示例中,我们有一个Visitor接口和两个具体的访问者方法(对应于两种不同类型的元素)。我们还有两个实现了Element接口的ConcreteElement类,它们各自有自己的操作。ObjectStructure类管理了一个Element对象的集合,并提供了一个accept方法来遍历这些对象并接受访问者的访问。在客户端代码中,我们创建了一个ObjectStructure对象,并向其中添加了两个不同类型的元素。然后,我们创建了一个ConcreteVisitor对象,并使用它来访问ObjectStructure中的所有元素。
访问者模式的应用
访问者模式是一种设计模式,它允许在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。这种模式在需要为数据结构中的元素添加新行为,但又不想修改这些元素所在类的情况下特别有用。
优点
- 扩展性好:当需要为数据结构中的元素添加新操作时,只需定义一个新的访问者类即可,无需修改原有类。
- 复用性好:访问者模式可以通过为不同的数据结构定义相同的访问者接口,实现操作的复用。
- 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可以相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则:每个访问者只负责一种操作,使代码更加清晰和易于维护。
缺点
- 破坏封装:在访问者模式中,具体元素需要向访问者公开其内部状态和方法,这可能会破坏对象的封装性。
增加新数据结构困难:当需要为新的数据结构添加访问者时,需要在访问者接口中添加新的方法,这可能会增加代码的复杂性。- 具体元素变更困难:如果具体元素的内部结构发生变化,可能需要修改所有相关的访问者类,这可能会增加维护成本。
使用场景
- 电商网站商品分类与操作:电商网站通常有大量的商品,这些商品可以按照不同的属性进行分类。使用访问者模式,可以定义一个访问者对象,它能够访问不同类型的商品对象,并根据需要执行不同的操作,如按照价格排序、根据品牌筛选等。
- 图形编辑器的操作处理:在图形编辑器中,有多种图形元素,如线条、圆形、矩形等。访问者模式允许定义一个访问者对象,它能够访问这些图形元素并执行不同的操作,如移动、缩放、旋转或改变颜色等。
- 编译器和解释器设计:在编译器或解释器的设计中,抽象语法树(
AST)是一个常见的数据结构。使用访问者模式,可以为AST中的不同节点类型定义不同的访问者,以执行如类型检查、代码优化、代码生成等操作。
将源程序表示为一个抽象语法树,编译器需要在抽象语法树上实施某些操作以进行“静态语义分析”,例如检查是否所有的变量都已经被定义了。他也需要生成代码。因此他可能要定义许多操作以进行类型检查、代码优化、流程分析,检查变量是否在使用前被赋初值,等等。此外,还可以使用抽象语法树进行优美格式打印、程序重构、code instrumentation(代码插装,见下面note注释内容)以及对程序进行多种度量。
这些操作大多要求对不同的节点进行不同的处理。例如对代表赋值语句的结点的处理就不同于对代表变量或算术表达式的结点的处理。
因此,有用于赋值语句的类,有用于变量访问的类,还有用于算术表达式的类等等。结点类的集合当然依赖于被编译的语言,但对于一个给定的语言其变化不大(即已有的数据结构变化不大或不变)。
note: Code instrumentation(代码仪器化)是一种软件开发领域的技术。详细解释如下:
- 定义
- Code instrumentation涉及向代码中插入特定的指令或代码片段,以便在程序执行过程中收集各种信息或执行特定的任务。
- 用途
- 调试:通过插入额外的代码,可以更容易地跟踪和调试程序的行为。
- 性能分析:插入的代码可以收集程序的性能数据,如执行时间、内存使用等。
- >代码覆盖率分析>:用于确定哪些代码在测试过程中被实际执行过。
- 安全检查:可以插入用于检测潜在安全问题的代码。
- 实现
- 通过在代码中嵌入仪器化代码,开发人员可以更深入地了解程序的执行过程,并获取关键的运行时信息。
- 其他表述
- Code instrumentation有时也被称为**“代码插装”或“代码插桩”**。
- 应用实例
- 在自动化测试和内部质量保证(QA)工具中,code instrumentation被用于基础设施、实施和维护,以及支持代码覆盖率的分析。
总结来说,code instrumentation是一种通过向代码中插入特定指令或代码片段来收集信息或执行任务的软件开发技术,它在调试、性能分析、代码覆盖率分析和安全检查等方面发挥着重要作用。
示例解析:博物馆的导览员
假设你是一家博物馆的导览员,博物馆里有很多不同类型的展品,如绘画、雕塑和古代文物。作为导览员,你需要为不同类型的展品提供不同的解说词。但是,展品的种类可能会随着时间增加或减少,而你不希望每次有新的展品加入时都要重新学习所有的解说词。
在这里,展品可以看作是数据结构中的元素,而导览员就是访问者。导览员(访问者)需要访问不同的展品(元素),并为它们提供解说词(操作)。
代码示例
下面是一个简单的Java代码示例,用于模拟这个场景:
package com.polaris.designpattern.list3.behavioral.pattern09.visitor.demo1;// 展品接口
interface Artifact {void accept(TourGuide tourGuide);
}// 绘画展品
class Painting implements Artifact {private String name;public Painting(String name) {this.name = name;}@Overridepublic void accept(TourGuide tourGuide) {tourGuide.visit(this);}public String getName() {return name;}
}// 雕塑展品
class Sculpture implements Artifact {private String name;public Sculpture(String name) {this.name = name;}@Overridepublic void accept(TourGuide tourGuide) {tourGuide.visit(this);}public String getName() {return name;}
}// 导览员(访问者)接口
interface TourGuide {void visit(Painting painting);void visit(Sculpture sculpture);// 如果以后有更多类型的展品,可以在这里添加方法
}// 具体导览员类
class EnglishTourGuide implements TourGuide {@Overridepublic void visit(Painting painting) {System.out.println("This is a painting called " + painting.getName() + ". It is beautiful!");}@Overridepublic void visit(Sculpture sculpture) {System.out.println("This is a sculpture called " + sculpture.getName() + ". It is impressive!");}
}// 客户端代码
public class MuseumDemo {public static void main(String[] args) {// 创建展品 Artifact painting = new Painting("Mona Lisa");Artifact sculpture = new Sculpture("Thinker");// 创建导览员 TourGuide tourGuide = new EnglishTourGuide();// 导览员访问展品 painting.accept(tourGuide);sculpture.accept(tourGuide);}
}/* Output:
This is a painting called Mona Lisa. It is beautiful!
This is a sculpture called Thinker. It is impressive!
*///~
在这个示例中,
Artifact是展品接口,Painting和Sculpture是具体的展品类。TourGuide是导览员接口,EnglishTourGuide是具体的导览员类。
当新的展品类型出现时,只需要实现Artifact接口并添加相应的accept方法,然后在TourGuide接口中添加对应的visit方法即可,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这样,就实现了在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。
note1: 当系统需要添加新的操作到已有的数据结构(如不同的展品类型)时,我们可以通过增加一个新的访问者类来实现,而无需修改原有的数据结构。
note2:当新的展品类型出现时,需要:
- 实现
Artifact接口来定义新的展品类(比如Ceramic代表陶瓷展品)。- 在
Artifact接口的实现类(如Ceramic)中,实现accept方法以允许导览员(TourGuide)访问。- 修改
TourGuide接口,添加一个新的visit方法来处理新的展品类型(比如void visit(Ceramic ceramic);)。- 在
TourGuide的实现类(如EnglishTourGuide)中,实现新添加的visit方法以提供针对新展品类型的解说词。这样,当新的展品类型被添加到博物馆时,导览员(
TourGuide)就能够处理它,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这种设计使得系统的扩展性很好,能够轻松应对新的展品类型的添加。
👈️上一篇:备忘录模式 | 下一篇:状态模式👉️
设计模式-专栏👈️
相关文章:
【设计模式深度剖析】【9】【行为型】【访问者模式】| 以博物馆的导览员为例加深理解
👈️上一篇:备忘录模式 | 下一篇:状态模式👉️ 设计模式-专栏👈️ 文章目录 访问者模式定义英文原话直译如何理解呢? 访问者模式的角色类图代码示例 访问者模式的应用优点缺点使用场景 示例解析:博物馆的导览员代码示例 访问…...
Salesforce‘s 爱因斯坦机器人助手引领工业聊天机器人时代
CRM的对话式人工智能助手,根据公司数据提供可靠的人工智能响应及日本工业聊天机器人现状 【前言】 爱因斯坦助手(Einstein Copilot)提供可靠的响应,因为它基于公司独特的数据和元数据,使其能够深入了解公司的业务和客…...
Day7—zookeeper基本操作
ZooKeeper介绍 ZooKeeper(动物园管理员)是一个分布式的、开源的分布式应用程序的协调服务框架,简称zk。ZooKeeper是Apache Hadoop 项目下的一个子项目,是一个树形目录服务。 ZooKeeper的主要功能 配置管理 分布式锁 集群管理…...
计算机组成原理---Cache的基本工作原理习题
对应知识点: Cache的基本原理 1.某存储系统中,主存容量是Cache容量的4096倍,Cache 被分为 64 个块,当主存地址和Cache地址采用直接映射方式时,地址映射表的大小应为()(假设不考虑一致维护和替…...
springboot项目中切数据库(mysql-> pg)带来的适配问题:typeHandler
一、数据表中有一张表,名为role_permission,DDL如下: CREATE TABLE "public"."role_permission" ( "role_id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL, "permiss…...
从零开始的<vue2项目脚手架>搭建:vite+vue2+eslint
前言 为了写 demo 或者研究某些问题,我经常需要新建空项目。每次搭建项目都要从头配置,很麻烦。所以我决定自己搭建一个项目初始化的脚手架(取名为 lily-cli)。 脚手架(scaffolding):创建项目时…...
Hadoop升级失败,File system image contains an old layout version -64
原始版本 Hadoop 3.1.3 升级版本 Hadoop 3.3.3 报错内容如下 datasophon 部署Hadoop版本 查看Hadoop格式化版本 which hadoop-daemon.sh/bigdata/app/hadoop-3.1.3/sbin/hadoop-daemon.sh删除原来的旧版本 rm -rf /bigdata/app/hadoop-3.1.3查看环境变量 env|grep HADOOPHAD…...
[机器学习算法]决策树
1. 理解决策树的基本概念 决策树是一种监督学习算法,可以用于分类和回归任务。决策树通过一系列规则将数据划分为不同的类别或值。树的每个节点表示一个特征,节点之间的分支表示特征的可能取值,叶节点表示分类或回归结果。 2. 决策树的构建…...
springboot应用cpu飙升的原因排除
1、通过top或者jps命令查到是那个java进程, top可以看全局那个进程耗cpu,而jps则默认是java最耗cpu的,比如找到进程是196 1.1 top (推荐)或者jps命令均可 2、根据第一步获取的进程号,查询进程里那个线程最占用cpu,发…...
反激开关电源EMI电路选型及计算
EMI :开关电源对电网或者其他电子产品的干扰 EMI :传导与辐射 共模电感的滤波电路,La和Lb就是共模电感线圈。这两个线圈绕在同一铁芯上,匝数和相位都相 同(绕制反向)。 这样,当电路中的正常电流(差模&…...
vue3前端对接后端的图片验证码
vue3前端对接后端的图片验证码 <template> <image :src"captchaUrl" alt"图片验证码" click"refreshCaptcha"></image> </template><script setup>import {ref} from "vue";import {useCounterStore} …...
【Unity】RPG2D龙城纷争(四)要诀、要诀数据集
更新日期:2024年6月20日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介要诀数据集(AbilityDataSet)一、定义要诀数据集类二、要诀属性1.要诀类型2.攻击距离3.基础命中、暴击率4.基础属性加成5.…...
一种基于非线性滤波过程的旋转机械故障诊断方法(MATLAB)
在众多的旋转机械故障诊断方法中,包络分析,又称为共振解调技术,是目前应用最为成功的方法之一。首先,对激励引起的共振频带进行带通滤波,然后对滤波信号进行包络谱分析,通过识别包络谱中的故障相关的特征频…...
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现(一) HarmonyOS Next 系列之验证码输入组件实现(二) HarmonyOS Next 系列之底部标签栏TabBar实现(三) HarmonyOS Next 系列之HTTP请求封装和Token…...
如果xml在mapper目录下,如何扫描到xml
如果xml在mapper目录下,如何扫描到xml 项目结构 src├── main│ ├── java│ │ └── com│ │ └── bg│ │ ├── Application.java│ │ ├── domain│ │ │ └── User.java│ │ …...
什么是无限铸币攻击?它是如何运作的?
一、无限铸币攻击解释 无限铸币攻击是指攻击者操纵合约代码不断铸造超出授权供应限制的新代币。 这种黑客行为在去中心化金融 (DeFi) 协议中最为常见。这种攻击通过创建无限数量的代币来损害加密货币或代币的完整性和价值。 例如,一名黑客利用了 Paid 网络的智能…...
【Android】怎么使APP进行开机启动
项目需求 在Android系统开启之后,目标app可以在系统开机之后启动。 项目实现 使用广播的方式 首先我们要创建一个广播(这里是启动了一个Service服务) public class BootReceiver extends BroadcastReceiver {Overridepublic void onReceive(Context context, I…...
详细分析Element Plus的el-pagination基本知识(附Demo)
目录 前言1. 基本知识2. Demo3. 实战 前言 需求:从无到有做一个分页并且附带分页的导入导出增删改查等功能 前提一定是要先有分页,作为全栈玩家,先在前端部署一个分页的列表 相关后续的功能,是Java,推荐阅读&#x…...
ubuntu换镜像源方法
查看ubuntu的版本,不同的版本对应的不同的镜像源 cat /etc/issue Ubuntu 18.04.6 LTS \n \l 先备份一个,防止更改错误 cobol cp /etc/apt/sources.list /etc/apt/sources.list.backup 先进入清华源,搜索ubuntu,点击问号 点进来可以看到可以…...
python flask配置邮箱发送功能,使用flask_mail模块
🌈所属专栏:【Flask】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询! 您的点…...
免费解锁付费内容:Bypass Paywalls Clean Chrome扩展终极指南
免费解锁付费内容:Bypass Paywalls Clean Chrome扩展终极指南 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 在数字阅读时代,你是否经常遇到想阅读的文章被付…...
STM32 USART串口调试避坑指南:从波特率配置到数据帧异常排查
STM32 USART串口调试避坑指南:从波特率配置到数据帧异常排查 在嵌入式开发中,USART串口通信是最基础却又最容易出问题的环节之一。许多开发者都曾经历过这样的场景:代码编译通过,硬件连接无误,但串口就是无法正常通信&…...
PostgreSQL开机启动踩坑实录:从‘服务不存在’到‘权限拒绝’的完整排错指南
PostgreSQL开机启动故障排查实战指南:从日志分析到权限修复 当你满怀期待地在服务器上执行systemctl start postgresql命令,却看到刺眼的红色报错信息时,那种挫败感我深有体会。作为一款强大的开源数据库,PostgreSQL在Linux系统上…...
告别特征点!FAST-LIVO2的‘直接法’融合:如何用原始点云和图像块实现更快的SLAM?
FAST-LIVO2:直接法SLAM的革命性突破与工程实践指南 1. 直接法SLAM的技术演进与核心价值 当波士顿动力的Atlas机器人完成后空翻动作时,其核心定位系统正面临着与人类体操运动员相似的挑战——如何在高速运动中维持对环境的精确感知。这正是FAST-LIVO2这类…...
FPGA音频播放器避坑指南:WM8731 I2C配置与左对齐时序的那些坑
FPGA音频播放器避坑指南:WM8731 I2C配置与左对齐时序的那些坑 第一次听到自己设计的FPGA音频播放器发出刺耳的噪音时,我盯着示波器上扭曲的波形陷入了沉思。作为嵌入式开发者,我们总在数字与模拟的交界处行走,而WM8731这颗看似简单…...
java毕业设计基于springboot铜仁一中学生成绩管理系统
前言 铜仁一中学生成绩管理系统是基于Java和Spring Boot框架开发的,目的是高效管理学生的成绩信息,为学校教学管理提供便利。通过该系统,教师可以方便地录入学生的各科考试成绩,学生和教师能够根据不同条件查询成绩,系…...
解向量前33位是DG位置,后33位是无功补偿容量
3.基于遗传算法的配电网优化配置 主要内容:分布式电源、无功补偿装置接入配电网,考虑配电网经济性和电能质量为目标函数,使用遗传算法进行优化配置,在IEEE33节点,118节点系统进行了仿真验证。 文件夹内运行main函数。配…...
低成本搭建QQ机器人:OpenClaw+nanobot消息中转方案
低成本搭建QQ机器人:OpenClawnanobot消息中转方案 1. 为什么选择OpenClawnanobot方案 去年我在管理一个小型技术社群时,经常需要处理重复性的问答和通知发布。尝试过多个机器人框架后,最终选择了OpenClawnanobot的组合方案。这个方案最吸引…...
为什么工作越久的精英,最后都放弃了 MBTI?
很多人在职场和生活中遇到瓶颈,第一反应是去测测 MBTI 或者大五人格。 甚至很多大厂在招聘时,也会把这些测试当作金标准。但我观察到一个现象:真正处于决策核心的高净值人群,早就开始放弃这些“自报式”的性格测试了。为什么&…...
OpenClaw技能扩展指南:为nanobot添加自定义QQ机器人功能
OpenClaw技能扩展指南:为nanobot添加自定义QQ机器人功能 1. 为什么需要QQ机器人集成 去年夏天,我发现自己经常在深夜调试代码时,需要反复切换手机和电脑查看运行结果。这种低效的操作让我开始寻找一种更优雅的解决方案——通过聊天工具直接…...
