当前位置: 首页 > news >正文

【设计模式深度剖析】【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.

直译

访问者模式是一种行为设计模式,它允许我们在不改变对象类的情况下为对象添加新的操作。访问者是一个类,它包含的方法对应于它所访问的对象结构中的类。客户端使用访问者来对对象结构的元素执行操作。

如何理解呢?

想象一下我们有一个装满各种形状(圆形、矩形、三角形)的盒子。现在我们想对这些形状进行不同的操作,比如测量它们的面积或周长。使用访问者模式,我们可以创建一个“测量工具”(即访问者),它知道如何与每种形状交互并获取所需的信息,而不需要改变形状本身。

访问者模式的角色

访问者模式中的角色包括:

  1. Visitor(访问者):这是一个接口或抽象类,声明了访问特定元素类时需要执行的操作。
  2. ConcreteVisitor(具体访问者):实现了Visitor接口或继承了Visitor抽象类,实现了对元素类中每个元素的访问操作。
  3. Element(元素):这是一个接口或抽象类,声明了一个接受访问者对象的方法(通常是accept)。
  4. ConcreteElement(具体元素):实现了Element接口或继承了Element抽象类,并实现accept方法以接受访问者的访问。
  5. 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中的所有元素。

访问者模式的应用

访问者模式是一种设计模式,它允许在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。这种模式在需要为数据结构中的元素添加新行为,但又不想修改这些元素所在类的情况下特别有用。

优点

  1. 扩展性好:当需要为数据结构中的元素添加新操作时,只需定义一个新的访问者类即可,无需修改原有类。
  2. 复用性好:访问者模式可以通过为不同的数据结构定义相同的访问者接口,实现操作的复用。
  3. 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可以相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则:每个访问者只负责一种操作,使代码更加清晰和易于维护。

缺点

  1. 破坏封装:在访问者模式中,具体元素需要向访问者公开其内部状态和方法,这可能会破坏对象的封装性。
  2. 增加新数据结构困难当需要为新的数据结构添加访问者时,需要在访问者接口中添加新的方法,这可能会增加代码的复杂性。
  3. 具体元素变更困难如果具体元素的内部结构发生变化,可能需要修改所有相关的访问者类,这可能会增加维护成本。

使用场景

  1. 电商网站商品分类与操作:电商网站通常有大量的商品,这些商品可以按照不同的属性进行分类。使用访问者模式,可以定义一个访问者对象,它能够访问不同类型的商品对象,并根据需要执行不同的操作,如按照价格排序、根据品牌筛选等。
  2. 图形编辑器的操作处理:在图形编辑器中,有多种图形元素,如线条、圆形、矩形等。访问者模式允许定义一个访问者对象,它能够访问这些图形元素并执行不同的操作,如移动、缩放、旋转或改变颜色等。
  3. 编译器和解释器设计:在编译器或解释器的设计中,抽象语法树(AST)是一个常见的数据结构。使用访问者模式,可以为AST中的不同节点类型定义不同的访问者,以执行如类型检查、代码优化、代码生成等操作。

将源程序表示为一个抽象语法树,编译器需要在抽象语法树上实施某些操作以进行“静态语义分析”,例如检查是否所有的变量都已经被定义了。他也需要生成代码。因此他可能要定义许多操作以进行类型检查、代码优化、流程分析,检查变量是否在使用前被赋初值,等等。此外,还可以使用抽象语法树进行优美格式打印、程序重构、code instrumentation(代码插装,见下面note注释内容)以及对程序进行多种度量。


这些操作大多要求对不同的节点进行不同的处理。例如对代表赋值语句的结点的处理就不同于对代表变量或算术表达式的结点的处理。


因此,有用于赋值语句的类,有用于变量访问的类,还有用于算术表达式的类等等。结点类的集合当然依赖于被编译的语言,但对于一个给定的语言其变化不大(即已有的数据结构变化不大或不变)。

note: Code instrumentation(代码仪器化)是一种软件开发领域的技术。详细解释如下:

  1. 定义
    • Code instrumentation涉及向代码中插入特定的指令或代码片段,以便在程序执行过程中收集各种信息或执行特定的任务。
  2. 用途
    • 调试:通过插入额外的代码,可以更容易地跟踪和调试程序的行为。
    • 性能分析:插入的代码可以收集程序的性能数据,如执行时间、内存使用等。
    • >代码覆盖率分析>:用于确定哪些代码在测试过程中被实际执行过。
    • 安全检查:可以插入用于检测潜在安全问题的代码。
  3. 实现
    • 通过在代码中嵌入仪器化代码,开发人员可以更深入地了解程序的执行过程,并获取关键的运行时信息。
  4. 其他表述
    • Code instrumentation有时也被称为**“代码插装”“代码插桩”**。
  5. 应用实例
    • 在自动化测试和内部质量保证(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是展品接口,PaintingSculpture是具体的展品类。TourGuide是导览员接口,EnglishTourGuide是具体的导览员类。


当新的展品类型出现时,只需要实现Artifact接口并添加相应的accept方法,然后在TourGuide接口中添加对应的visit方法即可,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这样,就实现了在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。

note1: 当系统需要添加新的操作到已有的数据结构(如不同的展品类型)时,我们可以通过增加一个新的访问者类来实现,而无需修改原有的数据结构。

note2:当新的展品类型出现时,需要:

  1. 实现Artifact接口来定义新的展品类(比如Ceramic代表陶瓷展品)。
  2. Artifact接口的实现类(如Ceramic)中,实现accept方法以允许导览员(TourGuide)访问。
  3. 修改TourGuide接口,添加一个新的visit方法来处理新的展品类型(比如void visit(Ceramic ceramic);)。
  4. TourGuide的实现类(如EnglishTourGuide)中,实现新添加的visit方法以提供针对新展品类型的解说词。

这样,当新的展品类型被添加到博物馆时,导览员(TourGuide)就能够处理它,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这种设计使得系统的扩展性很好,能够轻松应对新的展品类型的添加。


👈️上一篇:备忘录模式    |   下一篇:状态模式👉️

设计模式-专栏👈️

相关文章:

【设计模式深度剖析】【9】【行为型】【访问者模式】| 以博物馆的导览员为例加深理解

&#x1f448;️上一篇:备忘录模式 | 下一篇:状态模式&#x1f449;️ 设计模式-专栏&#x1f448;️ 文章目录 访问者模式定义英文原话直译如何理解呢&#xff1f; 访问者模式的角色类图代码示例 访问者模式的应用优点缺点使用场景 示例解析:博物馆的导览员代码示例 访问…...

Salesforce‘s 爱因斯坦机器人助手引领工业聊天机器人时代

CRM的对话式人工智能助手&#xff0c;根据公司数据提供可靠的人工智能响应及日本工业聊天机器人现状 【前言】 爱因斯坦助手&#xff08;Einstein Copilot&#xff09;提供可靠的响应&#xff0c;因为它基于公司独特的数据和元数据&#xff0c;使其能够深入了解公司的业务和客…...

Day7—zookeeper基本操作

ZooKeeper介绍 ZooKeeper&#xff08;动物园管理员&#xff09;是一个分布式的、开源的分布式应用程序的协调服务框架&#xff0c;简称zk。ZooKeeper是Apache Hadoop 项目下的一个子项目&#xff0c;是一个树形目录服务。 ZooKeeper的主要功能 配置管理 分布式锁 集群管理…...

计算机组成原理---Cache的基本工作原理习题

对应知识点&#xff1a; Cache的基本原理 1.某存储系统中&#xff0c;主存容量是Cache容量的4096倍&#xff0c;Cache 被分为 64 个块&#xff0c;当主存地址和Cache地址采用直接映射方式时&#xff0c;地址映射表的大小应为&#xff08;&#xff09;(假设不考虑一致维护和替…...

springboot项目中切数据库(mysql-> pg)带来的适配问题:typeHandler

一、数据表中有一张表&#xff0c;名为role_permission&#xff0c;DDL如下&#xff1a; CREATE TABLE "public"."role_permission" ( "role_id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL, "permiss…...

从零开始的<vue2项目脚手架>搭建:vite+vue2+eslint

前言 为了写 demo 或者研究某些问题&#xff0c;我经常需要新建空项目。每次搭建项目都要从头配置&#xff0c;很麻烦。所以我决定自己搭建一个项目初始化的脚手架&#xff08;取名为 lily-cli&#xff09;。 脚手架&#xff08;scaffolding&#xff09;&#xff1a;创建项目时…...

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. 理解决策树的基本概念 决策树是一种监督学习算法&#xff0c;可以用于分类和回归任务。决策树通过一系列规则将数据划分为不同的类别或值。树的每个节点表示一个特征&#xff0c;节点之间的分支表示特征的可能取值&#xff0c;叶节点表示分类或回归结果。 2. 决策树的构建…...

springboot应用cpu飙升的原因排除

1、通过top或者jps命令查到是那个java进程&#xff0c; top可以看全局那个进程耗cpu&#xff0c;而jps则默认是java最耗cpu的&#xff0c;比如找到进程是196 1.1 top (推荐)或者jps命令均可 2、根据第一步获取的进程号&#xff0c;查询进程里那个线程最占用cpu&#xff0c;发…...

反激开关电源EMI电路选型及计算

EMI &#xff1a;开关电源对电网或者其他电子产品的干扰 EMI &#xff1a;传导与辐射 共模电感的滤波电路&#xff0c;La和Lb就是共模电感线圈。这两个线圈绕在同一铁芯上&#xff0c;匝数和相位都相 同(绕制反向)。 这样&#xff0c;当电路中的正常电流&#xff08;差模&…...

vue3前端对接后端的图片验证码

vue3前端对接后端的图片验证码 <template> <image :src"captchaUrl" alt"图片验证码" click"refreshCaptcha"></image> </template><script setup>import {ref} from "vue";import {useCounterStore} …...

【Unity】RPG2D龙城纷争(四)要诀、要诀数据集

更新日期&#xff1a;2024年6月20日。 项目源码&#xff1a;第五章发布&#xff08;正式开始游戏逻辑的章节&#xff09; 索引 简介要诀数据集&#xff08;AbilityDataSet&#xff09;一、定义要诀数据集类二、要诀属性1.要诀类型2.攻击距离3.基础命中、暴击率4.基础属性加成5.…...

一种基于非线性滤波过程的旋转机械故障诊断方法(MATLAB)

在众多的旋转机械故障诊断方法中&#xff0c;包络分析&#xff0c;又称为共振解调技术&#xff0c;是目前应用最为成功的方法之一。首先&#xff0c;对激励引起的共振频带进行带通滤波&#xff0c;然后对滤波信号进行包络谱分析&#xff0c;通过识别包络谱中的故障相关的特征频…...

HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…...

如果xml在mapper目录下,如何扫描到xml

如果xml在mapper目录下,如何扫描到xml 项目结构 src├── main│ ├── java│ │ └── com│ │ └── bg│ │ ├── Application.java│ │ ├── domain│ │ │ └── User.java│ │ …...

什么是无限铸币攻击?它是如何运作的?

一、无限铸币攻击解释 无限铸币攻击是指攻击者操纵合约代码不断铸造超出授权供应限制的新代币。 这种黑客行为在去中心化金融 (DeFi) 协议中最为常见。这种攻击通过创建无限数量的代币来损害加密货币或代币的完整性和价值。 例如&#xff0c;一名黑客利用了 Paid 网络的智能…...

【Android】怎么使APP进行开机启动

项目需求 在Android系统开启之后&#xff0c;目标app可以在系统开机之后启动。 项目实现 使用广播的方式 首先我们要创建一个广播(这里是启动了一个Service服务) public class BootReceiver extends BroadcastReceiver {Overridepublic void onReceive(Context context, I…...

详细分析Element Plus的el-pagination基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 需求&#xff1a;从无到有做一个分页并且附带分页的导入导出增删改查等功能 前提一定是要先有分页&#xff0c;作为全栈玩家&#xff0c;先在前端部署一个分页的列表 相关后续的功能&#xff0c;是Java&#xff0c;推荐阅读&#x…...

ubuntu换镜像源方法

查看ubuntu的版本&#xff0c;不同的版本对应的不同的镜像源 cat /etc/issue Ubuntu 18.04.6 LTS \n \l 先备份一个&#xff0c;防止更改错误 cobol cp /etc/apt/sources.list /etc/apt/sources.list.backup 先进入清华源,搜索ubuntu&#xff0c;点击问号 点进来可以看到可以…...

python flask配置邮箱发送功能,使用flask_mail模块

&#x1f308;所属专栏&#xff1a;【Flask】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的点…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...