【设计模式】【行为型模式】访问者模式(Visitor)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 什么是访问者模式?
- 为什么需要访问者模式?
- 怎么实现访问模式?
- 二、访问者模式在源码中的运用
- ASM 框架(Java 字节码操作)
- 三、总结
- 访问者模式的优点
- 访问者模式的缺点
- 访问者模式的适用场景
一、入门
什么是访问者模式?
访问者模式(Visitor Pattern)是一种行为设计模式,允许你将算法与对象结构分离。通过这种方式,可以在不改变对象结构的情况下,向对象结构中的元素添加新的操作。
为什么需要访问者模式?
假设有一个图形对象结构,包含Circle和Rectangle两种元素,需要实现两种操作:计算面积和导出为JSON。
传统实现(无访问者模式):
// 元素类
interface Shape {double calculateArea(); // 操作1:计算面积String toJson(); // 操作2:导出为JSON
}class Circle implements Shape {@Overridepublic double calculateArea() { /* 实现 */ }@Overridepublic String toJson() { /* 实现 */ }
}class Rectangle implements Shape {@Overridepublic double calculateArea() { /* 实现 */ }@Overridepublic String toJson() { /* 实现 */ }
}
问题
- 违反开闭原则
- 当需要为对象结构添加新操作时(例如计算、导出、校验等),必须修改每个元素类的代码。
- 示例:每新增一个操作(如导出为XML),所有
Shape子类都需要修改。
- 代码冗余和分散
- 相关操作分散在各个元素类中,难以集中管理。
- 示例:如果“校验”逻辑分散在
Circle.validate()和Rectangle.validate()中,维护和扩展会变得困难。
- 难以扩展复杂操作
- 某些操作需要跨多个元素协作(例如统计图形的面积总合),直接写在元素类中会导致职责混乱。
怎么实现访问模式?
访问者模式的构成如下:
- Visitor(访问者):定义了对每个元素(Element)的访问操作,通常为每个具体元素类提供一个访问方法。
- ConcreteVisitor(具体访问者):实现Visitor接口,定义具体的操作。
- Element(元素):定义一个接受访问者的方法(accept),通常是一个接口或抽象类。
- ConcreteElement(具体元素):实现Element接口,提供具体的accept方法实现。
- ObjectStructure(对象结构):包含一组元素,通常提供一个方法让访问者访问其中的所有元素。
【案例】图形对象结构 - 改

Visitor(访问者接口):对应 ShapeVisitor 接口
作用:定义访问操作的接口,声明对不同具体元素(如Circle、Rectangle)的访问方法。
interface ShapeVisitor {void visit(Circle circle); // 访问 Circle 元素void visit(Rectangle rectangle); // 访问 Rectangle 元素
}
ConcreteVisitor(具体访问者):对应AreaCalculator和JsonExporter类
作用:实现 ShapeVisitor 接口,定义具体的操作逻辑(如计算面积、导出JSON)。
// 具体访问者1:计算面积
class AreaCalculator implements ShapeVisitor {@Overridepublic void visit(Circle circle) { /* 计算圆的面积 */ }@Overridepublic void visit(Rectangle rectangle) { /* 计算矩形的面积 */ }
}// 具体访问者2:导出为JSON
class JsonExporter implements ShapeVisitor {@Overridepublic void visit(Circle circle) { /* 导出圆的JSON */ }@Overridepublic void visit(Rectangle rectangle) { /* 导出矩形的JSON */ }
}
Element(元素接口):对应Shape接口
作用:定义元素的通用行为,即通过accept方法接受访问者。
interface Shape {void accept(ShapeVisitor visitor); // 接受访问者的入口
}
ConcreteElement(具体元素):对应Circle和Rectangle类
作用:实现Shape接口,在accept方法中将自身传递给访问者的具体方法(如visit(Circle))。
class Circle implements Shape {@Overridepublic void accept(ShapeVisitor visitor) {visitor.visit(this); // 调用访问者的 visit(Circle) 方法}
}class Rectangle implements Shape {@Overridepublic void accept(ShapeVisitor visitor) {visitor.visit(this); // 调用访问者的 visit(Rectangle) 方法}
}
ObjectStructure(对象结构):通常是一个管理元素集合的类。
作用:负责维护一组元素(如Shape对象),并提供遍历方法让访问者访问所有元素。
class ShapeCollection {private List<Shape> shapes = new ArrayList<>();public void addShape(Shape shape) {shapes.add(shape);}// 让访问者遍历所有元素public void accept(ShapeVisitor visitor) {for (Shape shape : shapes) {shape.accept(visitor);}}
}
二、访问者模式在源码中的运用
ASM 框架(Java 字节码操作)
ASM 框架是一个用于操作 Java 字节码的库,广泛用于动态生成类、修改类文件(如 AOP、代码增强)等场景。它通过访问者模式(Visitor Pattern)将字节码的解析和生成与具体操作解耦。
ASM 通过以下步骤实现字节码操作:
- 解析字节码:
ClassReader读取 .class 文件。 - 触发访问者:
ClassReader将字节码中的每个元素(类、方法、字段等)传递给ClassVisitr。 - 处理元素:开发者通过自定义的
ClassVisitor和MethodVisitor修改或分析字节码。 - 生成新字节码:
ClassWriter将修改后的字节码写入新的 .class 文件。
访问者模式角色 ASM 中的实现:
- Visitor:
ClassVisitor、MethodVisitor、FieldVisitor等接口 - ConcreteVisitor: 开发者自定义的
ClassVisitor、MethodVisitor实现 - Element: 字节码中的结构(类、方法、字段、指令等)
- ConcreteElement: 具体的类、方法、字段(如
visitMethod中的方法描述符) - ObjectStructure:
ClassReader(负责遍历类文件并触发访问者方法)
Visitor: 以ClassVisitor为例子,它的作用是,定义访问类结构的操作,例如访问类头、方法、字段等。
public abstract class ClassVisitor {protected final int api;protected ClassVisitor cv;...public void visit(final int version,final int access,final String name,final String signature,final String superName,final String[] interfaces) {if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) {throw new UnsupportedOperationException("Records requires ASM8");}if (cv != null) {cv.visit(version, access, name, signature, superName, interfaces);}}...
ConcreteVisitor:我们用户自己实现的Visitor接口的类
public class TimerClassVisitor extends ClassVisitor {public TimerClassVisitor(ClassVisitor cv) {super(Opcodes.ASM9, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);// 为所有非构造方法添加计时逻辑if (!name.equals("<init>")) {mv = new MethodTimerVisitor(mv, name);}return mv;}
}
Element:在 ASM 框架中,Element的概念是隐式的,而不是显式地通过一个接口或类来表示。字节码中的各种结构(如类、方法、字段、指令等)可以被视为Element,但它们并没有一个统一的接口或基类。相反,ASM 通过访问者模式直接操作这些结构。
在 ASM 中,以下结构可以被视为Element:
- 类:通过
ClassVisitor.visit方法的参数(如类名、父类、接口等)表示。 - 方法:通过
ClassVisitor.visitMethod方法的参数(如方法名、描述符等)表示。 - 字段:通过
ClassVisitor.visitField方法的参数(如字段名、类型等)表示。 - 指令:通过
MethodVisitor.visitInsn、visitMethodInsn等方法的参数表示。
ConcreteElement :在 ASM 框架中,ConcreteElement并不是通过一个显式的类或接口来表示的,而是通过访问者模式的方法参数隐式传递的。具体来说,字节码中的各种结构(如类、方法、字段、指令等)可以被视为ConcreteElement,但它们并没有一个统一的基类或接口。
在ClassVisitor.visit方法中,类的信息通过参数传递:
void visit(int version, // 类版本int access, // 访问标志(如 public、final)String name, // 类名String signature, // 泛型签名String superName, // 父类名String[] interfaces // 实现的接口
);
ObjectStructure:在 ASM 框架中,ClassReader就是这个角色,它负责读取类文件并触发访问者模式的方法调用。
以下是accept方法的简化伪代码,展示了ClassReader如何触发访问者方法:
public void accept(ClassVisitor cv, int parsingOptions) {// 解析类头cv.visit(version, access, name, signature, superName, interfaces);// 解析字段for (FieldInfo field : fields) {cv.visitField(field.access, field.name, field.desc, field.signature, field.value);}// 解析方法for (MethodInfo method : methods) {MethodVisitor mv = cv.visitMethod(method.access, method.name, method.desc, method.signature, method.exceptions);if (mv != null) {// 解析方法体for (Instruction insn : method.instructions) {mv.visitInsn(insn.opcode);}mv.visitEnd();}}// 结束访问cv.visitEnd();
}
三、总结
访问者模式的优点
- 开闭原则
- 优点:新增操作时只需添加新的访问者类,无需修改现有的对象结构。
- 示例:如果需要为类结构添加新的操作(如导出为XML),只需实现一个新的访问者类。
- 单一职责原则
- 优点:将相关操作集中在一个访问者类中,便于维护和扩展。
- 示例:将“计算面积”和“导出为JSON”的逻辑分别放在不同的访问者类中。
- 灵活性
- 优点:可以在不修改对象结构的情况下,动态地为对象结构添加新的操作。
- 示例:在编译器中使用访问者模式实现语法树的遍历和优化。
- 解耦数据结构与操作
- 优点:将数据结构与操作逻辑分离,使得代码更清晰、更易于维护。
- 示例:ASM 框架通过访问者模式将字节码解析与操作逻辑解耦。
访问者模式的缺点
- 增加新元素类型困难
- 缺点:每增加一种新元素类型,所有访问者类都需要修改。
- 示例:如果在类结构中新增一种元素(如注解),所有访问者类都需要添加对应的
visit方法。
- 破坏封装
- 缺点:访问者可能需要访问元素的私有成员,从而破坏封装性。
- 示例:访问者可能需要直接访问类的私有字段来完成某些操作。
- 复杂性增加
- 缺点:访问者模式引入了额外的类和接口,增加了代码的复杂性。
- 示例:需要定义
Visitor接口、ConcreteVisitor实现类以及Element接口。
- 性能开销
- 缺点:双重分派机制可能带来一定的性能开销。
- 示例:在性能敏感的场景中,访问者模式可能不如直接操作对象结构高效。
访问者模式的适用场景
- 对象结构稳定,但操作频繁变化
- 场景:对象结构很少变化,但需要频繁添加新的操作。
- 示例:编译器中的语法树遍历(如代码优化、静态分析)。
- 需要对对象结构进行多种不相关的操作
- 场景:对象结构需要支持多种不相关的操作,且希望将这些操作集中管理。
- 示例:文档对象模型(DOM)的遍历和操作(如渲染、导出、校验)。
- 操作需要跨多个元素协作
- 场景:某些操作需要访问多个元素并协作完成。
- 示例:统计文档中所有图片的尺寸总和。
- 避免污染元素类的代码
- 场景:希望保持元素类的简洁,避免将操作逻辑分散在各个元素类中。
- 示例:ASM 框架通过访问者模式避免将字节码操作逻辑分散在类、方法、字段等元素中。
相关文章:
【设计模式】【行为型模式】访问者模式(Visitor)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 🎵 当你的天空突…...
基于实例详解pytest钩子pytest_generate_tests动态生成测试的全过程
关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理,构建成功的基石 在自动化测试工作之前,你应该知道的10条建议 在自动化测试中,重要的不是工具 作为一名软件开发人员,你一定深知有效测试策略的重要性,尤其…...
Copilot基于企业PPT模板生成演示文稿
关于copilot创建PPT,咱们写过较多文章了: Copilot for PowerPoint通过文件创建PPT Copilot如何将word文稿一键转为PPT Copilot一键将PDF转为PPT,治好了我的精神内耗 测评Copilot和ChatGPT-4o从PDF创建PPT功能 Copilot for PPT全新功能&a…...
2025百度快排技术分析:模拟点击与发包算法的背后原理
一晃做SEO已经15年了,2025年还有人问我如何做百度快速排名,我能给出的答案就是:做好内容的前提下,多刷刷吧!百度的SEO排名算法一直是众多SEO从业者研究的重点,模拟算法、点击算法和发包算法是百度快速排名的…...
七星棋牌全开源修复版源码解析:6端兼容,200种玩法全面支持
本篇文章将详细讲解 七星棋牌修复版源码 的 技术架构、功能实现、二次开发思路、搭建教程 等内容,助您快速掌握该棋牌系统的开发技巧。 1. 七星棋牌源码概述 七星棋牌修复版源码是一款高度自由的 开源棋牌项目,该版本修复了原版中的多个 系统漏洞&#…...
解锁原型模式:Java 中的高效对象创建之道
系列文章目录 后续补充~~~ 文章目录 一、引言1.1 软件开发中的对象创建困境1.2 原型模式的登场 二、原型模式的核心概念2.1 定义与概念2.2 工作原理剖析2.3 与其他创建型模式的差异 三、原型模式的结构与角色3.1 抽象原型角色3.2 具体原型角色3.3 客户端角色3.4 原型管理器角色…...
DeepSeek从入门到精通:揭秘 AI 提示语设计误区与 AI 幻觉(新手避坑指南)
文章目录 引言常见陷阱与应对策略:新手必知的提示词设计误区缺乏迭代陷阱:期待一次性完美结果过度指令与模糊指令陷阱:当细节缺乏重点或意图不明确假设偏见陷阱:当前 AI 只听你想听的幻觉生成陷阱:当AI自信地胡说八道忽…...
Jenkins同一个项目不同分支指定不同JAVA环境
背景 一些系统应用,会为了适配不同的平台,导致不同的分支下用的是不同的gradle,导致需要不同的JAVA环境来编译,比如a分支需要使用JAVA11, b分支使用JAVA17。 但是jenkins上,一般都是Global Tool Configuration 全局所有环境公用一个JAVA_HOME。 尝试过用 Build 的Execut…...
从入门到精通:Postman 实用指南
Postman 是一款超棒的 API 开发工具,能用来测试、调试和管理 API,大大提升开发效率。下面就给大家详细讲讲它的安装、使用方法,再分享些实用技巧。 一、安装 Postman 你能在 Postman 官网(https://www.postman.com )下…...
win32汇编环境,对话框中使用月历控件示例二
;运行效果 ;win32汇编环境,对话框中使用月历控件示例二 ;以下示例有2个操作,即将每周的开始日进行改变,将默认的周日开始改为周一开始,同时实现点击哪个日期,则设定为哪个日期 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>>>>>>&…...
gsoap实现webservice服务
gsoap实现webservice服务 在实现Web服务时,使用gSOAP是一个很好的选择,因为它提供了强大的工具和库来创建SOAP和RESTful服务。gSOAP是一个C和C语言开发的库,它支持SOAP协议的各种版本,包括SOAP 1.1和SOAP 1.2。下面是如何使用gSO…...
容联云联络中心AICC:深度整合DeepSeek,业务验证结果公开
容联云重磅推出AICC3.2版本,实现了智能化的升级与外呼效率的突破——深度整合DeepSeek-R1大模型、预测式外呼在数据分析侧的增强、全渠道路由能力、一键多呼效率的强化。 同时,全面接入DeepSeek-R1的容联云 AICC3.2 ,目前已与某知名汽车金融企…...
腿足机器人之七- 逆运动学
腿足机器人之七- 逆运动学 基本概念腿部运动的数学表示坐标系定义以及自由度说明正运动学模型 逆运动学求解几何解法数值迭代法雅可比矩阵法基础双足机器人步态规划中的雅可比法应用 工程挑战与解决方案实际应用中的工具和算法多解问题高自由度机器人(如Atlas的28自…...
快速点位排查问题的方法
一、核心思路:缩小问题范围 1. 分治法(Divide and Conquer) 原理:将复杂系统拆分为独立模块,逐层验证。示例: 网络问题:检查客户端 → 本地网络 → 服务器 → 数据库。代码问题:注…...
【前端】Vue组件库之Element: 一个现代化的 UI 组件库
文章目录 前言一、官网1、官网主页2、设计原则3、导航4、组件 二、核心功能:开箱即用的组件生态1、丰富的组件体系2、特色功能亮点 三、快速上手:三步开启组件化开发1、安装(使用Vue 3)2、全局引入3、按需导入(推荐&am…...
一文搞懂Android应用元素查看器(Appium+Appium-inspector)——定位微信布局元素
Appium和Appium Inspector是怎么协作的呢?Appium 与 Appium Inspector 的版本匹配Appium安装启动appium服务安装Appium inspector客户端查看安卓真机指定app布局元素(这里以微信为例,需要保持与模拟器或真机一直连接)【QA】解决顶部工具栏上Refresh Source & Screensho…...
matlab质子磁力仪传感器线圈参数绘图
1、内容简介 matlab134-质子磁力仪传感器线圈参数绘图 可以交流、咨询、答疑 2、内容说明 略 线圈是质子磁力仪传感器的核心,其品质直接影响着仪器的测量精度 。 结合反向串联圆柱体线圈模型,对约束设计 的因素进行分析; 建立约束参数与设计参数之间…...
WPF快速创建DeepSeek本地自己的客户端-基础思路版本
开发工具:VS 2015 开发环境:.Net 4.0 使用技术:WPF 本篇文章内容: 本地部署DeepSeek以后一般使用网页工具(如Chatbox)或者DOS窗口与其对话。本篇文章使用WPF创建一个基础版的对话工具。 一、搭建本地DeepS…...
FreeRTOS第12篇:系统的“绿色通道”——中断管理与临界区
文/指尖动听知识库-星愿 文章为付费内容,商业行为,禁止私自转载及抄袭,违者必究!!! 文章专栏:深入FreeRTOS内核:从原理到实战的嵌入式开发指南 引言:嵌入式系统的“紧急电话” 想象你正在主持一场重要会议:大部分时间按议程推进(任务执行),但偶尔会有紧急来电(硬…...
SpringBoot+Vue+数据可视化的动漫妆造服务平台(程序+论文+讲解+安装+调试+售后等)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在当今数字化高速发展的时代,动漫产业迎来了前所未有的繁荣,动漫…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
