设计模式之访问者模式:一楼千面 各有玄机
一、访问者模式概述
\quad 江湖中有一个传说:在遥远的东方,有一座神秘的玉楼。每当武林中人来访,楼中的各个房间都会根据来访者的身份展现出不同的面貌。练剑之人来访,便见剑术精要;习医之人来访,则现医道真谛。一样的楼阁,却能因来访者的不同而呈现万千气象。这,正是访问者模式的真谛。
\quad 在软件设计的世界里,访问者模式就像这座神奇的玉楼。它允许我们将数据结构和数据操作分离,就像将楼阁和访客分开一样。这种设计模式定义了一种方式,让我们能够在不改变已有对象结构的情况下,向其中添加新的操作行为。
\quad 想象一下游乐园的场景:过山车、旋转木马、海盗船等设施早已固定在那里,但每天都会有不同的人来访问它们 —— 游客来游玩、检查员来检修、维护工来保养。每类访问者都会对这些设施进行不同的操作,但设施本身的结构并不会因此改变。
二、访问者模式的角色组成
\quad 在解析访问者模式的角色构成之前,让我们先看一下它的整体结构::
\quad 就像一座精心设计的园林,访问者模式中的每个角色都各司其职,共同构建出一个优雅的结构体系。让我们一起走进这座代码园林,认识一下其中的主要角色:
- Element(元素):它就像游乐园中的各个游乐设施。每个Element都定义了一个accept方法,这个方法就像设施的接待窗口,来访者必须通过这个窗口才能与设施互动。在我们的游乐园例子中,它可以是过山车、旋转木马等具体设施。
- Visitor(访问者):它就像是来游乐园的不同人员。可能是来玩耍的游客、来检修的工程师,或是来检查安全的督察员。每种访问者都定义了一系列visit方法,用于访问不同类型的元素。这些方法就像是不同人员对设施的不同操作方式。
- ObjectStructure(对象结构):像是整个游乐园的管理处。它知道园内有哪些设施,并且负责安排访问者去访问这些设施。当一个安全检查员来到游乐园时,管理处会安排他依次检查所有的设施。
- ConcreteElement(具体元素):是Element的实现类,就像具体的过山车、旋转木马。它们都实现了accept方法,在方法中通过调用访问者的visit方法来完成具体的操作。这就像每个设施都知道如何配合不同人员的工作。
- ConcreteVisitor(具体访问者):Visitor的实现类,例如具体的安全检查员、维修工程师等。他们各自实现了visit方法,定义了对不同设施的具体操作流程。检查员检查安全隐患,工程师进行维护保养,各司其职。
\quad 这些角色之间的互动就像是一场精心编排的舞蹈:当游客(ConcreteVisitor)来到游乐园(ObjectStructure)时,管理处会安排他们依次游览各个设施(ConcreteElement)。每个设施都会根据访问者的身份,展现出相应的互动方式。
三、访问者模式案例
\quad 让我们通过一个完整的游乐园管理系统来深入理解访问者模式。在这个系统中,我们需要对不同的游乐设施进行日常检查和维护。每种设施都有其特定的检查点,而不同的工作人员(访问者)也有着不同的工作职责。
\quad 首先,让我们定义设施接口和具体设施:
// 设施接口
public interface Facility {void accept(FacilityVisitor visitor);
}// 过山车设施
public class RollerCoaster implements Facility {private String name;private int maxSpeed;public RollerCoaster(String name, int maxSpeed) {this.name = name;this.maxSpeed = maxSpeed;}public String getName() { return name; }public int getMaxSpeed() { return maxSpeed; }@Overridepublic void accept(FacilityVisitor visitor) {visitor.visit(this);}
}// 旋转木马设施
public class Carousel implements Facility {private String name;private int capacity;public Carousel(String name, int capacity) {this.name = name;this.capacity = capacity;}public String getName() { return name; }public int getCapacity() { return capacity; }@Overridepublic void accept(FacilityVisitor visitor) {visitor.visit(this);}
}// 访问者接口
public interface FacilityVisitor {void visit(RollerCoaster rollerCoaster);void visit(Carousel carousel);
}// 安全检查员
public class SafetyInspector implements FacilityVisitor {@Overridepublic void visit(RollerCoaster rollerCoaster) {System.out.println("安全检查员正在检查过山车 " + rollerCoaster.getName());System.out.println("检查最高速度: " + rollerCoaster.getMaxSpeed() + "km/h");System.out.println("检查安全带和刹车系统...");}@Overridepublic void visit(Carousel carousel) {System.out.println("安全检查员正在检查旋转木马 " + carousel.getName());System.out.println("检查承载人数: " + carousel.getCapacity() + "人");System.out.println("检查座椅固定装置...");}
}// 维护工程师
public class MaintenanceEngineer implements FacilityVisitor {@Overridepublic void visit(RollerCoaster rollerCoaster) {System.out.println("维护工程师正在保养过山车 " + rollerCoaster.getName());System.out.println("润滑轨道和车轮...");System.out.println("检测电机运行状态...");}@Overridepublic void visit(Carousel carousel) {System.out.println("维护工程师正在保养旋转木马 " + carousel.getName());System.out.println("检查驱动系统...");System.out.println("更换磨损零件...");}
}// 游乐园管理类
public class AmusementPark {private List<Facility> facilities = new ArrayList<>();public void addFacility(Facility facility) {facilities.add(facility);}public void accept(FacilityVisitor visitor) {for(Facility facility : facilities) {facility.accept(visitor);}}
}
\quad 现在让我们通过一个具体的例子来运行这个系统:
public class Test{public static void main(String[] args) {// 创建游乐园AmusementPark park = new AmusementPark();// 添加设施park.addFacility(new RollerCoaster("极速之星", 120));park.addFacility(new Carousel("童话木马", 30));// 创建访问者SafetyInspector inspector = new SafetyInspector();MaintenanceEngineer engineer = new MaintenanceEngineer();// 进行安全检查System.out.println("=== 开始安全检查 ===");park.accept(inspector);System.out.println("\n=== 开始设备维护 ===");park.accept(engineer);}
}
\quad 运行这段代码,我们可以看到不同的访问者对相同设施进行不同的操作,而不需要修改设施类的代码:
四、访问者模式优缺点
4.1. 优点:
\quad 访问者模式最显著的特点是实现了数据结构与数据操作的分离。就像我们的游乐园案例,无论是增加新的检查员还是维护工程师,都不需要修改原有的设施类代码。这种设计非常符合"开闭原则",对扩展开放,对修改关闭。同时,相关的操作行为被集中在访问者类中,使得操作逻辑更加集中和清晰。例如,所有的安全检查逻辑都在SafetyInspector类中,便于统一管理和维护。
4.2. 缺点:
\quad 首先是对扩展元素类型不友好。如果我们要在游乐园中增加一种全新的设施类型,就需要修改所有现有的访问者类,添加相应的visit方法。这违反了"开闭原则"。其次,访问者模式要求元素类的内部结构对访问者是可见的。比如检查员需要知道过山车的最高速度、旋转木马的承载人数等属性,这在某种程度上破坏了对象的封装性。
\quad 此外,使用访问者模式可能会导致系统变得更复杂。我们需要维护多个访问者类,它们之间可能存在一些交叉的职责。比如安全检查和维护工作可能会有重叠的检查项目,这时就需要考虑如何合理划分职责。
五、访问者模式的适用场景
\quad 访问者模式就像是一位经验丰富的管家,最适合处理"对象结构相对稳定,但操作多种多样"的场景。除了我们讨论的游乐园管理系统,它在许多其他领域也有着广泛的应用。
\quad 比如在编译器设计中,语法树的结构一旦确定就不会改变,但我们需要对语法树进行词法分析、语法分析、代码生成等多种操作。又如在文档处理系统中,文档结构(段落、章节、图表等)相对固定,但我们需要对文档进行打印、预览、格式转换等不同操作。
\quad 当你发现系统中有一个复杂的对象结构,而且经常需要对这些对象进行不同的操作时,不妨考虑使用访问者模式。但如果对象结构经常变动,或者操作比较单一,使用访问者模式可能会适得其反。
六、总结
\quad 访问者模式,讲究的是进退有度,相互尊重。它通过巧妙的设计,让数据结构与数据操作得以分离,就像是让每位访客都能以最适合的方式与主人互动。
\quad 在实际应用中,我们要明智地选择是否使用访问者模式。当面对稳定的对象结构和多变的操作需求时,访问者模式如同一位老成持重的管家,能够有条不紊地处理各种访客的需求。但如果对象结构经常变动,或者操作相对单一,使用访问者模式反而会使系统变得臃肿复杂。
\quad 正如古人云:万物有度,过犹不及。设计模式也是如此,关键在于找到最适合当前场景的解决方案。访问者模式,不过是我们设计工具箱中的一件利器,懂得何时使用,方能游刃有余。
相关文章:

设计模式之访问者模式:一楼千面 各有玄机
~犬📰余~ “我欲贱而贵,愚而智,贫而富,可乎? 曰:其唯学乎” 一、访问者模式概述 \quad 江湖中有一个传说:在遥远的东方,有一座神秘的玉楼。每当武林中人来访,楼中的各个房…...

AI 编程的世界:用Cursor编写评分项目
AI 编程的世界:用Cursor编写评分项目 今天是2024年的最后一天,祝大家在新的一年,健康开心快乐! 岁末之际,星辰为伴,灯火长明,我终于在 2024 年的最后一天成功上线了 AI 编程项目。回首这一年&am…...

Cesium教程(二十三):Cesium实现下雨场景
文章目录 实现效果代码引入js文件创建容器创建视图定义下雨场景完整代码下载实现效果 代码 在 Cesium 中利用PostProcessStageLibrary实现下雪场景,你可以按照以下步骤进行: 创建一个 PostProcessStage:首先,你需要创建一个PostProcessStage对象,它将用于定义下雪效果的渲…...

SpringCloudAlibaba技术栈-Higress
1、什么是Higress? 云原生网关,干啥的?用通俗易懂的话来说,微服务架构下Higress 就像是一个智能的“交通警察”,它站在你的网络世界里,负责指挥和调度所有进出的“车辆”(也就是数据流量)。它的…...

uniapp 微信小程序开发使用高德地图、腾讯地图
一、高德地图 1.注册高德地图开放平台账号 (1)创建应用 这个key 第3步骤,配置到项目中locationGps.js 2.下载高德地图微信小程序插件 (1)下载地址 高德地图API | 微信小程序插件 (2)引入项目…...

Springboot:后端接收数组形式参数
1、接收端写法 PermissionAnnotation(permissionName "",isCheckToken true)PostMapping("/batchDeleteByIds")public ReturnBean webPageSelf( NotNull(message "请选择要删除的单据!") Long[] ids) {for (Long string : ids) {l…...

Postman[2] 入门——界面介绍
可参考官方 文档 Postman 导航 | Postman 官方帮助文档中文版Postman 拥有各种工具、视图和控件,帮助你管理 API 项目。本指南是对 Postman 主要界面区域的高级概述:https://postman.xiniushu.com/docs/getting-started/navigating-postman 1. Header&a…...

1月第四讲:Java Web学生自习管理系统
一、项目背景与需求分析 随着网络技术的不断发展和学校规模的扩大,学生自习管理系统的需求日益增加。传统的自习管理方式存在效率低下、资源浪费等问题,因此,开发一个智能化的学生自习管理系统显得尤为重要。该系统旨在提高自习室的利用率和…...

【Redis】Redis 典型应用 - 缓存 (cache)
目录 1. 什么是缓存 2. 使用 Redis 作为缓存 3. 缓存的更新策略 3.1 定期生成 3.2 实时生成 4. 缓存的淘汰策略 5. 缓存预热, 缓存穿透, 缓存雪崩 和 缓存击穿 关于缓存预热 (Cache preheating) 关于缓存穿透 (Cache penetration) 关于缓存雪崩 (Cache avalanche) 关…...

HTML——38.Span标签和字符实体
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…...

ROS2+OpenCV综合应用--10. AprilTag标签码追踪
1. 简介 apriltag标签码追踪是在apriltag标签码识别的基础上,增加了小车摄像头云台运动的功能,摄像头会保持标签码在视觉中间而运动,根据这一特性,从而实现标签码追踪功能。 2. 启动 2.1 程序启动前的准备 本次apriltag标签码使…...

python Celery 是一个基于分布式消息传递的异步任务队列系统
Celery 是一个基于分布式消息传递的异步任务队列系统,主要用于处理耗时任务、定时任务和周期性任务。它能够将任务分配到多个工作节点(Worker)上执行,从而提高应用程序的性能和可扩展性。Celery 是 Python 生态中最流行的任务队列…...

嵌入式硬件杂谈(七)IGBT MOS管 三极管应用场景与区别
引言:在现代嵌入式硬件设计中,开关元件作为电路中的重要组成部分,起着至关重要的作用。三种主要的开关元件——IGBT(绝缘栅双极型晶体管)、MOSFET(金属氧化物半导体场效应晶体管)和三极管&#…...

麒麟信安云在长沙某银行的应用入选“云建设与应用领航计划(2024)”,打造湖湘金融云化升级优质范本
12月26日,2024云计算产业和标准应用大会在北京成功召开。大会汇集政产学研用各方专家学者,共同探讨云计算产业发展方向和未来机遇,展示云计算标准化工作重要成果。 会上,云建设与应用领航计划(2024)建云用…...

好用的随机生成图片的网站
官网: Lorem Picsum 获取自定义大小的随机图像 https://picsum.photos/200/300 获取正方形图像 https://picsum.photos/200 获取特定类型的图像 通过添加到 /id/{image} url 的开头来获取特定图像。 https://picsum.photos/id/237/200/300 获取静态随机图像…...

添加 env 配置,解决import路径问题
添加 env 配置,解决import路径问题 { // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid830387 “version”: “0.2.0”, “configurations”: [ {"name&q…...

Go work stealing 机制
Go语言的Work Stealing(工作窃取)机制是一种用于调度Goroutines(协程)的策略,其核心目的是最大化CPU使用率,减少任务调度的开销,并提高并发性能和吞吐量。以下是Go Work Stealing机制的详细解释…...

基础数据结构--二叉树
一、二叉树的定义 二叉树是 n( n > 0 ) 个结点组成的有限集合,这个集合要么是空集(当 n 等于 0 时),要么是由一个根结点和两棵互不相交的二叉树组成。其中这两棵互不相交的二叉树被称为根结点的左子树和右子树。 如图所示&am…...

《C++设计模式》策略模式
文章目录 1、引言1.1 什么是策略模式1.2 策略模式的应用场景1.3 本文结构概览 2、策略模式的基本概念2.1 定义与结构2.2 核心角色解析2.2.1 策略接口(Strategy)2.2.2 具体策略实现(ConcreteStrategy)2.2.3 上下文(Cont…...

JavaScript学习记录6
第一节 算数运算符 1. 概述 JavaScript 共提供10个算术运算符,用来完成基本的算术运算。 加法运算符x y减法运算符 x - y乘法运算符 x * y除法运算符x / y指数运算符x ** y余数运算符x % y自增运算符x 、x自减运算符--x 、x--数值运算符 x负数值运算符-x 减法、…...

如何在没有 iCloud 的情况下将数据从 iPhone 传输到 iPhone
概括 您可能会遇到将数据从 iPhone 转移到 iPhone 的情况,尤其是当您获得新的 iPhone 15/14 时,您会很兴奋并希望将数据转移到它。 使用iCloud最终可以做到这一点,但它的缺点也不容忽视,阻碍了你选择它。例如,您需要…...

Doris安装部署
Doris 概述 Apache Doris由百度大数据部研发(之前叫百度 Palo,2018年贡献到 Apache 社区后,更名为 Doris ),在百度内部,有超过200个产品线在使用,部署机器超过1000台,单一业务最大可…...

[服务器][教程]Ubuntu24.04 Server开机自动挂载硬盘教程
1. 查看硬盘ID ls -l /dev/disk/by-uuid可以看到对应的UUID所对应的分区 2. 创建挂载文件夹 创建好文件夹即可 3. 修改配置文件 sudo vim /etc/fstab把对应的UUID和创建的挂载目录对应即可 其中# Personal mount points下面的是自己新添加的 :分区定位ÿ…...

io多路复用, select, poll, epoll
系列文章目录 异步I/O操作函数aio_xxx函数 https://blog.csdn.net/surfaceyan/article/details/134710393 文章目录 系列文章目录前言一、5种IO模型二、IO多路复用APIselectpollepoll 三、两种高效的事件处理模式Reactor模式Proactor模式模拟 Proactor 模式基于事件驱动的非阻…...

k8s-1.28.2 部署prometheus
一、prometheus helm仓库 ## 网站地址 # https://artifacthub.io/## prometheus 地址 # https://artifacthub.io/packages/helm/prometheus-community/prometheus. # helm repo add prometheus-community https://prometheus-community.github.io/helm-charts # helm repo …...

记录第一次跑YOLOV8做目标检测
今天是24年的最后一天,终于要向新世界开始破门了,开始深度学习,YOLO来敲门~ 最近做了一些皮肤检测的功能,在传统的处理中经历了反复挣扎,终于要上YOLO了。听过、看过,不如上手体会过~ 1、YOLO是什么&#x…...

使用Python爬取BOSS直聘职位数据并保存到Excel
使用Python爬取BOSS直聘职位数据并保存到Excel 在数据分析和挖掘中,爬取招聘网站数据是一项常见的任务。本文将详细介绍如何使用Python爬取BOSS直聘上与“测试工程师”相关的职位数据,并将其保存到Excel文件中。通过逐步分解代码和添加详细注释…...

node.js之---集群(Cluster)模块
为什么会有集群(Cluster)模块? 集群(Cluster)模块的作用 如何使用集群(Cluster)模块? 为什么会有集群(Cluster)模块 Node.js 是基于 单线程事件驱动 模型的…...

SSM-Spring-IOC/DI对应的配置开发
目录 一、IOC 控制反转 1.什么是控制反转呢 2. Spring和IOC之间的关系是什么呢? 3.IOC容器的作用以及内部存放的是什么? 4.当IOC容器中创建好service和dao对象后,程序能正确执行么? 5.Spring 容器管理什么内容? 6.如何将需要管理的对象交给 …...

一文大白话讲清楚CSS元素的水平居中和垂直居中
文章目录 一文大白话讲清楚CSS元素的水平居中和垂直居中1.已知元素宽高的居中方案1.1 利用定位margin:auto1.2 利用定位margin负值1.3 table布局 2.未知元素宽高的居中方案2.1利用定位transform2.2 flex弹性布局2.3 grid网格布局 3. 内联元素的居中布局 一文大白话讲清楚CSS元素…...