访问者模式 (Visitor Pattern)

定义

访问者模式(Visitor Pattern)是一种行为型设计模式,用于将算法与其作用于的对象结构分离。这种模式主要用于执行操作或应用过程,这些操作需要在不同类型的对象上执行,同时避免让这些对象的类变得过于复杂。

关键组成部分

  1. 访问者(Visitor)
    • 一个接口或抽象类,定义了对不同类型元素(Element)的访问操作。
    • 实现了每种类型元素的操作,是将操作逻辑从元素类中分离出来的关键所在。
  2. 元素(Element)
    • 定义了一个接受访问者的方法(通常为 accept),该方法允许访问者对象访问元素。
    • 元素结构通常稳定,且含有多个接受访问的方法,每个方法对应一种类型的访问者。
  3. 具体元素(Concrete Element)
    • 实现元素接口,定义了 accept 方法的具体实现。
    • 可能有多个不同类型的具体元素类,每个类都有自己的逻辑和接受访问者的方式。
  4. 具体访问者(Concrete Visitor)
    • 实现访问者接口,定义了对每个元素类的具体操作。
    • 可能有多个不同类型的具体访问者,每个访问者都实现了一套作用于元素的操作。
解决的问题
  • 操作与对象结构的分离:在复杂对象结构中,经常需要执行各种不依赖于特定对象的操作。访问者模式使得可以将这些操作从对象结构中分离出来,以减少这些操作对于对象结构的影响。
  • 添加新操作的灵活性:当新的操作需要在这些对象上执行时,你可能不希望更改这些对象的类。访问者模式允许你通过添加新的访问者类来添加新的操作,而无需修改对象的类。
  • 集中相关操作:在传统的面向对象设计中,相关的操作可能分散在各个类中。访问者模式允许你将相关操作集中在一个访问者类中,这样可以避免在对象结构中散布这些操作,从而提高代码的组织性和可维护性。
  • 扩展性:对于那些可能需要添加新操作的对象结构,访问者模式提供了一种容易扩展的方式。你可以在不更改现有代码的情况下,通过创建新的访问者来添加新的操作。
  • 聚合操作:在一些情况下,你可能需要对一个复杂的对象结构执行聚合操作,如遍历、搜索或生成报告。访问者模式使得这些操作可以被集中管理和维护。
使用场景
  • 复杂对象结构:在复杂的对象结构中(如树状或图状结构),需要对结构中的各个对象执行操作,而这些操作依赖于对象的具体类型。访问者模式允许在不修改这些对象类的情况下,添加新的操作。
  • 添加新操作:当需要对一个对象结构添加新的操作,且不希望这些操作影响到对象的类时。访问者模式允许将操作逻辑封装在访问者中,易于扩展。
  • 避免"污染"对象类:如果在每个对象类中添加新操作会导致类变得复杂或不易维护,那么使用访问者模式将这些操作外部化是一个好选择。
  • 不同的访问者实现不同的操作:当同一个对象结构需要支持多种不同的操作,且这些操作是互相独立的。例如,可能有一个用于渲染对象的访问者,另一个用于检查对象的完整性。
  • 频繁变更的操作:如果一组操作经常变更,但对象结构相对稳定,那么将这些操作作为访问者的一部分,可以避免频繁修改对象结构。
  • 累积状态:在遍历一个复杂结构时,如果需要在访问者中累积状态,而不是在元素中累积,那么访问者模式也是一个不错的选择。
示例代码1-计算机部件访问者

在这个例子中,我们定义了一个计算机部件(ComputerPart)的接口和一些具体部件类(Keyboard、Monitor、Mouse),以及一个访问者接口(ComputerPartVisitor)和一个具体的访问者实现(ComputerPartDisplayVisitor)。

// 访问者接口
interface ComputerPartVisitor {void visit(Computer computer);void visit(Mouse mouse);void visit(Keyboard keyboard);void visit(Monitor monitor);
}// 元素接口
interface ComputerPart {void accept(ComputerPartVisitor computerPartVisitor);
}// 元素实现
class Keyboard implements ComputerPart {public void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}class Monitor implements ComputerPart {public void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}class Mouse implements ComputerPart {public void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}class Computer implements ComputerPart {ComputerPart[] parts;public Computer(){parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};		}public void accept(ComputerPartVisitor computerPartVisitor) {for (int i = 0; i < parts.length; i++) {parts[i].accept(computerPartVisitor);}computerPartVisitor.visit(this);}
}// 具体访问者
class ComputerPartDisplayVisitor implements ComputerPartVisitor {public void visit(Computer computer) {System.out.println("Displaying Computer.");}public void visit(Mouse mouse) {System.out.println("Displaying Mouse.");}public void visit(Keyboard keyboard) {System.out.println("Displaying Keyboard.");}public void visit(Monitor monitor) {System.out.println("Displaying Monitor.");}
}// 客户端
public class VisitorPatternDemo {public static void main(String[] args) {ComputerPart computer = new Computer();computer.accept(new ComputerPartDisplayVisitor());}
}
示例代码2-媒体文件的操作

在这个例子中,我们有不同类型的媒体文件(如音频文件和视频文件),并希望执行不同的操作,例如播放和编码。我们将定义媒体文件的接口和具体类,以及一个访问者接口和两个具体的访问者实现。

// 媒体文件接口
interface MediaFile {void accept(MediaFileVisitor visitor);
}// 音频文件
class AudioFile implements MediaFile {private String filename;public AudioFile(String filename) {this.filename = filename;}public String getFilename() {return filename;}@Overridepublic void accept(MediaFileVisitor visitor) {visitor.visit(this);}
}// 视频文件
class VideoFile implements MediaFile {private String filename;public VideoFile(String filename) {this.filename = filename;}public String getFilename() {return filename;}@Overridepublic void accept(MediaFileVisitor visitor) {visitor.visit(this);}
}// 媒体文件访问者接口
interface MediaFileVisitor {void visit(AudioFile audio);void visit(VideoFile video);
}// 播放操作访问者
class PlayVisitor implements MediaFileVisitor {@Overridepublic void visit(AudioFile audio) {System.out.println("Playing audio file: " + audio.getFilename());}@Overridepublic void visit(VideoFile video) {System.out.println("Playing video file: " + video.getFilename());}
}// 编码操作访问者
class EncodeVisitor implements MediaFileVisitor {@Overridepublic void visit(AudioFile audio) {System.out.println("Encoding audio file: " + audio.getFilename());}@Overridepublic void visit(VideoFile video) {System.out.println("Encoding video file: " + video.getFilename());}
}public class VisitorDemo {public static void main(String[] args) {MediaFile audio = new AudioFile("song.mp3");MediaFile video = new VideoFile("movie.mp4");MediaFileVisitor playVisitor = new PlayVisitor();MediaFileVisitor encodeVisitor = new EncodeVisitor();audio.accept(playVisitor);video.accept(playVisitor);audio.accept(encodeVisitor);video.accept(encodeVisitor);}
}

在这个例子中,MediaFile 接口定义了接受访问者的方法。AudioFileVideoFile 是具体的媒体文件类。MediaFileVisitor 接口定义了访问者的行为,而 PlayVisitorEncodeVisitor 是具体的访问者实现,它们实现了对不同媒体文件进行播放和编码的操作。这样,当需要为媒体文件添加新的操作时,我们只需要添加新的访问者,而不必修改媒体文件类。

主要符合的设计原则
  • 开闭原则(Open-Closed Principle)
    • 访问者模式允许在不修改现有代码的情况下引入新的操作。这意味着类可以保持开放以供扩展(通过添加新的访问者来实现新的功能),但对修改是关闭的(因为你不需要改变现有的类和对象结构)。
  • 单一职责原则(Single Responsibility Principle)
    • 在访问者模式中,元素类的职责是维护其核心功能和数据,而访问者类的职责是执行在这些元素上的特定操作。这种分离确保了单一职责原则,即每个类或模块只有一个原因导致改变。
  • 依赖倒置原则(Dependency Inversion Principle)
    • 访问者模式通常定义了抽象访问者和抽象元素接口,具体的访问者和元素类都依赖于这些接口,而不是具体的实现。这符合依赖倒置原则,即高层模块不应该依赖于低层模块的具体实现,而应该依赖于抽象。
  • 里氏替换原则(Liskov Substitution Principle)
    • 在访问者模式中,可以用子类的对象替换父类的对象,而程序的行为不会发生变化。比如在访问者模式中,可以用具体元素的子类来替换父类元素,而访问者的行为不会改变。
在JDK中的应用
  • Java文件I/O(NIO)
    • java.nio.file.FileVisitor 接口是访问者模式的一个经典应用。它用于遍历文件系统的目录树。FileVisitor 接口定义了在访问目录树的过程中可以执行的一系列操作(如访问文件前后的操作),而具体的行为则由实现了 FileVisitor 接口的类定义。
    • SimpleFileVisitor 类是 FileVisitor 的一个实现,它提供了对遍历过程中各种事件的基本处理。
  • Java编译API
    • javax.lang.model 包中,Java编译API使用了访问者模式来处理抽象语法树(AST)。这个API允许开发者在编译时检查、处理和生成Java代码。
    • ElementElementVisitor 接口及其相关类在这个包中用于表示和访问AST中的元素。
  • Java反射API
    • java.lang.reflect 包中,反射API提供了一种访问者风格的接口,用于检查类和对象的运行时行为。例如,Visitor 模式用于在不同类型的 AnnotatedElement(如类、方法、字段等)上执行操作。
在Spring中的应用
  • Spring框架中没有直接的访问者模式实例,但是框架的设计允许并鼓励使用访问者模式来实现跨多个类的操作和维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/167553.shtml

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

相关文章

【Python 训练营】N_5 斐波那契数列

题目 输出斐波那契数列 分析 斐波那契数列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又称黄金分割数列&#xff0c;指的是这样一个数列&#xff1a;0、1、1、2、3、5、8、13、21、34、……。 在数学上&#xff0c;费波那契数列是以递归的方法来定义&#xff…

9.9 Windows驱动开发:内核远程线程实现DLL注入

在笔者上一篇文章《内核RIP劫持实现DLL注入》介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的&#xff0c;本章将继续探索全新的注入方式&#xff0c;通过NtCreateThreadEx这个内核函数实现注入DLL的目的&#xff0c;需要注意的是该函数在微软系统中未被导出使用时需要首…

用XMind2TestCase,测试更轻松

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

C++ Qt QByteArray用法介绍

作者:令狐掌门 技术交流QQ群:675120140 csdn博客:https://mingshiqiang.blog.csdn.net/ 文章目录 一、QByteArray的基本用法1、初始化和赋值2、访问和修改元素3、 常用方法4、数据转换二、QByteArray与文件操作三、QByteArray与网络编程四、QByteArray数据编码1、Base64 编解…

数据库-MySQL之数据库必知必会10-13章

第10章 创建计算字段 拼接字段 使用Concat()函数 执行算术计算 示例&#xff1a;从 Products 表中返回 prod_id、prod_price 和 sale_price。sale_price 是一个包含促销价格的计算字段。提示&#xff1a;可以乘以 0.9&#xff0c;得到原价的 90%&#xff08;即 10%的折扣&…

2023.11.24 海豚调度,postgres库使用

目录 海豚调度架构dolphinscheduler DAG(Directed Acyclic Graph)&#xff0c; 个人自用启动服务 DS的架构(海豚调度) 海豚调度架构dolphinscheduler 注:需要先开启zookeeper服务,才能进行以下操作 通过UI进行工作流的配置操作, 配置完成后, 将其提交执行, 此时执行请求会被…

数组基础知识

数组基础&#xff08;不定时更新&#xff09; 数组基础 数组基础 &#xff08;1&#xff09;数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。数组下标都是从0开始的。数组内存空间的地址是连续的。 &#xff08;…

【科普知识】什么是步进电机?

德国百格拉公司于1973年发明了五相混合式步进电机及其驱动器&#xff0c;1993年又推出了性能更加优越的三相混合式步进电机。我国在80年代以前&#xff0c;一直是反应式步进电机占统治地位&#xff0c;混合式步进电机是80年代后期才开始发展。 步进电机是一种用电脉冲信号进行…

Verilog基础:时序调度中的竞争(一)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 作为一个硬件描述语言&#xff0c;Verilog HDL常常需要使用语句描述并行执行的电路&#xff0c;但其实在仿真器的底层&#xff0c;这些并行执行的语句是有先后顺序…

机器学习数据集整理:图像、表格

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 表格数据 Sklearn 提供了 13 个表格型数据&#xff0c;且数据处理接口统一&#xff1b;LIBSVM 提供了 131 个表格型数据&a…

【TypeScript】常见数据结构与算法(二):链表

文章目录 链表结构&#xff08;LinkedList&#xff09;链表以及数组的缺点数组链表的优势 什么是链表?封装链表相关方法源码链表常见面试题237-删除链表中的节点206 - 反转链表 数组和链表的复杂度对比 链表结构&#xff08;LinkedList&#xff09; 链表以及数组的缺点 链表…

AcWing103.电影——离散化

题目 莫斯科正在举办一个大型国际会议&#xff0c;有 n n n 个来自不同国家的科学家参会。 每个科学家都只懂得一种语言。 为了方便起见&#xff0c;我们把世界上的所有语言用 1 到 109 之间的整数编号。 在会议结束后&#xff0c;所有的科学家决定一起去看场电影放松一下。…

Interactive Visual Data Analysis

Words&Contents Home | Interactive Visual Data Analysis Book Outline 这本书对视觉、互动和分析方法进行了系统而全面的概述&#xff0c;作为数据可视化方面比较好的读物&#xff1b; 目录 Words&Contents Book Outline &#xff08;一&#xff09;Introduct…

AIGC 3D即将爆发,混合显示成为产业数字化的生产力平台

2023年&#xff0c;大语言模型与生成式AI浪潮席卷全球&#xff0c;以文字和2D图像生成为代表的AIGC正在全面刷新产业数字化。而容易为市场所忽略的是&#xff0c;3D图像生成正在成为下一个AIGC风口&#xff0c;AIGC 3D宇宙即将爆发。所谓AIGC 3D宇宙&#xff0c;即由文本生成3D…

VBA_MF系列技术资料1-227

MF系列VBA技术资料 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-04属于定…

安装compiler version 5

这个compiler version5 在我的资源里面可以免费下载&#xff1b; 另外这个东西还需要安装&#xff0c;安装教程在这里&#xff1a;Keil最新版保姆教程&#xff08;解决缺少V5编译器问题&#xff09; - 哔哩哔哩 (bilibili.com) 看吧安装好了year

C语言链表使用

目录 双链表增删改查链表带功能函数 双链表增删改查 #include <stdio.h> #include <stdlib.h>// 双链表结点的定义 typedef struct DNode{int data;struct DNode *prev;struct DNode *next; } DNode;// 创建双链表 DNode *createDoublyLinkedList() {int n, i;pri…

【C语言】qsort的秘密

一&#xff0c;本文目标 qsort函数可以对任意类型数据甚至是结构体内部的数据按照你想要的规则排序&#xff0c;它的功能很强大&#xff0c;可是为什么呢&#xff1f; 我将通过模拟实现qsort函数来让你对这整个过程有一个清晰的深刻的理解。 二&#xff0c;qsort函数原型 v…

leetcode刷题详解一

算法题常用API std::accumulate 函数原型&#xff1a; template< class InputIt, class T > T accumulate( InputIt first, InputIt last, T init );一般求和的&#xff0c;代码如下&#xff1a; int sum accumulate(vec.begin() , vec.end() , 0);详细用法参考 lo…

【python海洋专题四十七】风速的风羽图

【python海洋专题四十七】风速的风羽图 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【python海洋专题三】图像修饰之画布和坐标轴 【Python海洋专题四】之水深地图图像修饰 【Pyth…