设计模式-访问者模式(Visitor)
设计模式-访问者模式(Visitor)
- 一、访问者模式概述
- 1.1 什么是访问者模式
- 1.2 简单实现访问者模式
- 1.3 使用访问者模式的注意事项
- 二、访问者模式的用途
- 三、访问者模式实现方式
- 3.1 递归遍历实现访问者模式
- 3.2 迭代遍历实现访问者模式
- 3.3 Java8 Stream API 实现访问者模式
一、访问者模式概述
1.1 什么是访问者模式
访问者模式(Visitor Pattern)是一种将算法与对象结构分离的软件设计模式。它的基本思想是让访问者对象能够遍历一个或多个被访问对象,并根据需要对它们执行操作。
在访问者模式中,被访问对象通常有一个接受访问者的方法,该方法接受一个访问者对象作为参数。访问者对象则定义了一个用于访问被访问对象的接口,该接口包含一组方法,每个方法对应于被访问对象的一个操作。
访问者模式的优点包括:
-
1、将被访问对象和访问者解耦,使得它们可以独立地变化而不影响彼此;
-
2、支持递归遍历,使得访问者可以处理复杂的数据结构;
-
3、可以很容易地添加新的操作到被访问对象和访问者中,而不需要修改现有的代码。
访问者模式的缺点包括: -
1、如果被访问对象和访问者的接口发生变化,可能需要修改大量的代码;
-
2、如果被访问对象和访问者的数量很大,可能会导致系统的性能下降。
1.2 简单实现访问者模式
首先,我们定义一个访问者接口Visitor,它包含一个visit方法,用于访问不同类型的元素:
public interface Visitor {void visit(ElementType1 element);void visit(ElementType2 element);// ...其他元素类型
}
然后,我们创建一个具体的访问者类ConcreteVisitor,实现Visitor接口,并重写visit方法以执行相应的操作:
public class ConcreteVisitor implements Visitor {@Overridepublic void visit(ElementType1 element) {// 对ElementType1类型的元素执行操作System.out.println("处理ElementType1类型的元素");}@Overridepublic void visit(ElementType2 element) {// 对ElementType2类型的元素执行操作System.out.println("处理ElementType2类型的元素");}// ...其他元素类型的处理方法
}
接下来,我们定义一个元素接口Element,它包含一个accept方法,用于接受访问者:
public interface Element {void accept(Visitor visitor);
}
然后,我们创建一些具体的元素类,实现Element接口,并重写accept方法以调用访问者的visit方法:
public class ConcreteElementType1 implements Element {@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}
}public class ConcreteElementType2 implements Element {@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}
}// ...其他元素类型的实现
最后,我们可以在主程序中使用访问者模式来处理元素:
public class Main {public static void main(String[] args) {Visitor visitor = new ConcreteVisitor();Element element1 = new ConcreteElementType1();Element element2 = new ConcreteElementType2();element1.accept(visitor);element2.accept(visitor);}
}
运行上述代码,将输出:
处理ElementType1类型的元素
处理ElementType2类型的元素
1.3 使用访问者模式的注意事项
-
1、确定何时使用访问者模式:访问者模式适用于需要对一组对象进行操作,而这些对象的操作又不相同的情况。如果对象之间的操作相同,则应该考虑使用其他设计模式。
-
2、将对象和操作分离:访问者模式的核心思想是将对象和操作分离,使得可以独立地改变它们。因此,在实现访问者模式时,必须确保对象和操作的分离。
-
3、避免滥用递归:访问者模式通常使用递归来遍历对象结构。但是,如果对象结构过于复杂或递归深度过大,可能会导致性能问题。因此,在使用访问者模式时,应该避免滥用递归。
-
4、考虑使用其他设计模式:访问者模式是一种较为复杂的设计模式,使用时需要考虑其优缺点。在某些情况下,可以考虑使用其他更简单的设计模式来解决问题。
-
5、保持代码简洁:访问者模式可能会使代码变得复杂,因此应该尽可能地保持代码简洁。可以通过使用辅助类、封装访问者逻辑等方式来实现这一点。
二、访问者模式的用途
访问者模式是一种将作用于某种数据结构中各元素的操作分离出来封装成独立的类,使其访问者模式是一种将作用于某种数据结构中各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作的设计模式。它主要适用于对一组对象进行操作,而这些对象的操作又不相同的情况。
以下是访问者模式的主要应用场景:
-
1、对象结构相对稳定,但其操作算法经常变化的程序。这是因为访问者模式能够分离出针对对象的操作,当需要改变操作时,只需更改访问者类的代码,而无需修改对象结构的代码。
-
2、对象结构复杂,且需要对每一个对象执行不同的操作。如果直接在对象上执行操作,可能会让代码变得复杂难以理解。使用访问者模式可以将复杂的操作分解为多个访问者类,使得代码更加清晰和易于维护。
-
3、需要遍历一个复杂的对象结构。访问者模式通常使用递归来遍历对象结构,如果对象结构过于复杂或递归深度过大,可能会导致性能问题。但在一些情况下,如医院药单处理系统等,访问者模式可以有效地简化并优化遍历过程。
总的来说,访问者模式是一种强大的设计模式,它提供了一种灵活可扩展的方式来处理复杂的对象结构和操作,但同时也需要注意其复杂性和可能的性能问题。
三、访问者模式实现方式
3.1 递归遍历实现访问者模式
递归遍历实现访问者模式的步骤如下:
- 1、定义一个访问者接口,包含访问元素的方法。
- 2、定义一个元素类,包含一个访问者列表和一个接受访问者的方法。
- 3、在元素类中实现递归遍历方法,遍历子元素并调用它们的访问方法。
- 4、创建一个具体的访问者类,实现访问者接口。
- 5、创建一个元素对象,添加子元素和访问者。
- 6、调用元素的递归遍历方法。
以下是一个简单的示例:
// 访问者接口
interface Visitor {void visit(Element element);
}// 元素类
class Element {private List<Element> children = new ArrayList<>();private Visitor visitor;public void accept(Visitor visitor) {this.visitor = visitor;visitor.visit(this);for (Element child : children) {child.accept(visitor);}}public void addChild(Element child) {children.add(child);}
}// 具体的访问者类
class ConcreteVisitor implements Visitor {@Overridepublic void visit(Element element) {System.out.println("访问元素: " + element);}
}public class Main {public static void main(String[] args) {Element root = new Element();Element child1 = new Element();Element child2 = new Element();root.addChild(child1);root.addChild(child2);Visitor visitor = new ConcreteVisitor();root.accept(visitor);}
}
这个示例中,我们创建了一个元素类Element,它包含一个访问者列表和一个接受访问者的方法。我们还创建了一个具体的访问者类ConcreteVisitor,实现了访问者接口。在主方法中,我们创建了一个元素对象,添加了子元素和访问者,然后调用了元素的递归遍历方法。
3.2 迭代遍历实现访问者模式
- 1、定义一个访问者接口,包含访问元素的方法。
- 2、定义一个元素类,包含一个访问者列表和一个接受访问者的方法。
- 3、在元素类中实现迭代遍历方法,遍历子元素并调用它们的访问方法。
- 4、创建一个具体的访问者类,实现访问者接口。
- 5、创建一个元素对象,添加子元素和访问者。
- 6、调用元素的迭代遍历方法。
// 访问者接口
interface Visitor {void visit(Element element);
}// 元素类
class Element {private List<Element> children = new ArrayList<>();private Visitor visitor;public void accept(Visitor visitor) {this.visitor = visitor;visitor.visit(this);Iterator<Element> iterator = children.iterator();while (iterator.hasNext()) {Element child = iterator.next();child.accept(visitor);}}public void addChild(Element child) {children.add(child);}
}// 具体的访问者类
class ConcreteVisitor implements Visitor {@Overridepublic void visit(Element element) {System.out.println("访问元素:" + element);}
}public class Main {public static void main(String[] args) {Element root = new Element();Element child1 = new Element();Element child2 = new Element();root.addChild(child1);root.addChild(child2);Visitor visitor = new ConcreteVisitor();root.accept(visitor);}
}
这个示例中,我们创建了一个元素类Element,它包含一个访问者列表和一个接受访问者的方法。我们还创建了一个具体的访问者类ConcreteVisitor,实现了访问者接口。在主方法中,我们创建了一个元素对象,添加了子元素和访问者,然后调用了元素的迭代遍历方法
3.3 Java8 Stream API 实现访问者模式
要使用Java 8 Stream API实现访问者模式,首先需要定义一个访问者接口,然后创建一个具体的访问者类实现该接口。接下来,使用Stream API对集合进行操作,并在操作过程中调用访问者的相应方法。以下是一个简单的示例:
定义访问者接口:
public interface Visitor {void visit(Element element);
}
创建具体的访问者类实现访问者接口:
public class ConcreteVisitor implements Visitor {@Overridepublic void visit(Element element) {// 在这里处理元素的逻辑System.out.println("访问元素: " + element);}
}
使用Stream API对集合进行操作,并在操作过程中调用访问者的相应方法:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class Main {public static void main(String[] args) {List<Element> elements = Arrays.asList("A", "B", "C", "D");// 使用Stream API对集合进行操作,并在操作过程中调用访问者的相应方法List<Element> result = elements.stream().map(element -> {System.out.println("处理元素: " + element);return element;}).collect(Collectors.toList());System.out.println("处理后的元素列表: " + result);}
}
在这个示例中,我们首先定义了一个访问者接口Visitor,然后创建了一个具体的访问者类ConcreteVisitor实现了该接口。在main方法中,我们使用Stream API对一个元素列表进行操作,并在操作过程中调用访问者的visit方法。
相关文章:

设计模式-访问者模式(Visitor)
设计模式-访问者模式(Visitor) 一、访问者模式概述1.1 什么是访问者模式1.2 简单实现访问者模式1.3 使用访问者模式的注意事项 二、访问者模式的用途三、访问者模式实现方式3.1 递归遍历实现访问者模式3.2 迭代遍历实现访问者模式3.3 Java8 Stream API 实…...

C++二分查找算法:132 模式解法二枚举2
题目及解法一: https://blog.csdn.net/he_zhidan/article/details/134362273 分析 第一步,选择各3对应的1,如果有多个符合对应最小的1,记录num[0,j)中的最小值iMin,如果nums[j]大于iMin,则m3To1 [nums[j…...

JavaWeb-HTML
一、什么是HTML HTML是hypertext markup language(超文本标记语言)的缩写。HTML文件本质上是文本文件,普通的文本文件只能显示字符,而HTML文件可以在浏览器上显示更丰富的信息(如图片等)。 超文本&am…...

新外卖霸王餐小程序、H5、微信公众号版外卖系统源码
最新外卖霸王餐小程序、H5、微信公众号版外卖系统源码、霸王餐美团、饿了么系统,粉丝裂变玩源码下载,外卖cps小程序项目,外卖红包cps带好友返利佣金分销系统程序、饿了么美团联盟源码,外卖cps带分销返利后端源码,基于L…...

LeetCode - #89 格雷编码
文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 LeetCode 算法到目前我们已经更新…...

11.3SpringMVC
一.概念 1.SpringMvc: a.构建在Servlet(api)基础上. b.是一个Web框架(HTTP). c.来自于Spring webMVC模块. 2.MVC 二.注册路由的注解 1.RequestMapping("/test") // 路由注册 注意: 这个注解在类和方法上都要使用,代表不同等级的路由. 2.RestController a)R…...

c语言从入门到实战——数组指针与函数指针
数组指针与函数指针 前言1. 字符指针变量2. 数组指针变量2.1 数组指针变量是什么?2.2 数组指针变量怎么初始化? 3. 二维数组传参的本质4. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使用4.3 两段有趣的代码4.3.1 typedef关键字 5. 函数指针数组6. 转移…...

Rust图形界面编程:egui平直布局
文章目录 平直布局with_layout 平直布局 在前面的示例中,已经用到了ui.horizontal用来布局,其特点是水平摆放控件。相应地,ui.vertical则是垂直摆放控件。根据控件的摆放顺序不同,这两个布局组件衍生出一系列布局函数 horizonta…...

Android13 wifi adb 串口开启
Android13 wifi adb 串口开启 文章目录 Android13 wifi adb 串口开启一、前言二、开启wifi adb1、开启wifi adb 命令:2、查看和设置 adb默认值3、adb 开启属性prop和settings属性的关系 三、总结1、Android13 开启adb 串口命令2、Android 13 wifi adb设置固定端口解…...

关于一个屏幕取词程序,AI给的创建思路及指导
我:我在windows上,经常碰到各种软件当中有自己不认识的英文,请问如果要用python开发一个随时添加屏幕上任意英文单词到生词词典中的软件,该怎么进行? AI:开发一个能够从屏幕上捕获英文单词并将其添加到生词…...

MySql跨库跨表触发器
一、跨库触发器的概念 跨库触发器是指能在一个数据库中创建的触发器,但触发器的操作涉及到其他数据库中的表。这种触发器的存在可以帮助我们实现一些复杂的业务逻辑,比如在一个数据库中的表更新时,自动更新另一个数据库中的相关表。 二、创建…...

NextJS开发:shadcn/ui中Button组件扩展增加图标
shadcn/ui组件比较灵活,但是功能相比ant之类组件还是缺少太多功能,本文为shadcn/ui为button组件增加图标,加载中动画等效果。 安装Lucide npm install lucideLucide组件 import { cn } from /lib/utils; import { icons } from lucide-rea…...

Go 语言
1. 请简要介绍一下 Go 语言的特点。 Go 语言是一种高性能、并发支持强大且易于学习的编程语言。以下是 Go 语言的一些主要特点: 高性能:Go 语言的运行速度接近 C 和 Java,某些场景下甚至更快,这使得它非常适合用于高性能计算和网…...

【计算机网络笔记】DHCP协议
系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…...

21 Linux 自带的LED驱动
一、Linux 自带 LED 驱动使能 其实 Linux 内核自带 LED 抢夺那个,但在此之前需要配置 Linux 驱动来使能 LED 驱动。 输入以下命令: cd linux/atk-mpl/linux/my_linux/linux-5.4.31 make menuconfig 根据以下路径找到 LED 驱动: → Device D…...

神通MPP数据库的跨库查询
神通MPP数据库的跨库查询 一. 简介二. 系统表三. 跨库查询语法1. 创建外部数据存储服务器2. 删除外部数据存储服务器3. 授予普通用户访问外部数据存储服务器权限4. 回收普通用户访问外部数据存储服务器权限5. 加密函数6. 访问外部数据存储服务器 ★ 四. 跨库查询:统…...

JavaWeb-WEB请求过程
WEB请求过程 一、B/S架构1.1 BS结构的好处1.2 B/S架构是如何完成交互的1.3 B/S网络架构的核心HTTP1.3.1 HTTP请求头1.3.2 HTTP响应头1.3.3 HTTP状态码1.3.4 HTTP缓存机制二、DNS域名解析、CND(分发网络)、负载均衡2.1 DNS域名解析2.2 CDN工作机制2.3 负载均衡2.3.1 硬件负载均衡…...

《QT从基础到进阶·二十一》QGraphicsView、QGraphicsScene和QGraphicsItem坐标关系和应用
前言: 我们需要先由一个 QGraphicsView,这个是UI显示的地方,也就是装满可见原色的Scene,然后需要一个QGraphicsScene 用来管理所有可见的界面元素,要实现UI功能,我们需要用各种从QGraphicsItem拼装成UI控件…...

32 _ 字符串匹配基础(上):如何借助哈希算法实现高效字符串匹配?
从今天开始,我们来学习字符串匹配算法。字符串匹配这样一个功能,我想对于任何一个开发工程师来说,应该都不会陌生。我们用的最多的就是编程语言提供的字符串查找函数,比如Java中的indexOf(),Python中的find()函数等,它们底层就是依赖接下来要讲的字符串匹配算法。 字符串…...

TCP怎么实现可靠传输
链接 1,TCP头部的校验和保证获取正确数据,防篡改; 2,序列号和ACK确认机制同于管理数据包,对接收到的数据包进行确认,对没有接收到的数据包进行重传; 3,重传机制,包括超…...

C# new 和 override 的区别
在C#中子类继承抽象类的时候,new 和override都可以用来修饰子类方法,但两者之间是有区别的。 相同点: 它们都是子类在覆写基类方法时,修饰子类同名方法用的,都是为了隐藏基类的同名方法在实例化子类对象的时候&#…...

C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』
✨个人主页: 北 海 🎉所属专栏: C修行之路 🎃操作环境: Visual Studio 2022 版本 17.6.5 文章目录 🌇前言🏙️正文1.右值引用1.1.什么是右值引用?1.2.move 转移资源1.3.左值引用 vs …...

在Windows以命令行方式根据文件名称搜索文件
对于cmd.exe,使用:dir /s /b filename.extension /s选项表示在子目录中搜索文件,/b选项表示仅显示文件名而不显示其他信息 对于PowerShell,使用:Get-ChildItem -Path “C:” -Recurse -Filter filename.extension -Re…...

asp.net数字档案管理系统VS开发sqlserver数据库web结构c#编程web网页设计
一、源码特点 asp.net 数字档案管理系统 是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语 言开发。 asp.net数字档案系统1 应用技…...

数据挖掘 决策树
# 编码声明,并不是注释,而是一种特殊的源文件指令,用于指定文件的字符编码格式 # -*- coding: utf-8 -*-import pandas as pd # 提供了DataFrame等数据结构 from sklearn.tree import DecisionTreeClassifier, export_graphviz # 决策树分类…...

“技能兴鲁”职业技能大赛-网络安全赛项-学生组初赛 WP
Crypto BabyRSA 共模攻击 题目附件: from gmpy2 import * from Crypto.Util.number import *flag flag{I\m not gonna tell you the FLAG} # 这个肯定不是FLAG了,不要交这个咯p getPrime(2048) q getPrime(2048) m1 bytes_to_long(bytes(flag.e…...

[Android]修改应用包名、名称、版本号、Icon以及环境判断和打包
1.修改包名 在Android Studio中更改项目的包名涉及几个步骤: 打开项目结构: 在Android Studio中,确保您处于Android视图模式(在左侧面板顶部有一个下拉菜单可以选择)。 重命名包名: 在项目视图中,找到您的包名&…...

基于风驱动算法优化概率神经网络PNN的分类预测 - 附代码
基于风驱动算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于风驱动算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于风驱动优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要:针对PNN神经网络…...

安全计算环境(设备和技术注解)
网络安全等级保护相关标准参考《GB/T 22239-2019 网络安全等级保护基本要求》和《GB/T 28448-2019 网络安全等级保护测评要求》 密码应用安全性相关标准参考《GB/T 39786-2021 信息系统密码应用基本要求》和《GM/T 0115-2021 信息系统密码应用测评要求》 1身份鉴别 1.1对登录的…...

【Hello Go】Go语言函数
Go语言函数 定义格式自定义函数无参数无返回值有参数无返回值不定参数列表有返回值有多个返回值 函数类型匿名函数和闭包延迟调用deferdefer和匿名函数结合使用 获取命令行参数 定义格式 函数是构成代码执行的逻辑结构 在Go语言中 函数的基本组成为 func关键字函数名参数列表…...