Java访问者模式源码剖析及使用场景

news/2024/5/30 1:45:59/文章来源:https://blog.csdn.net/weixin_44716935/article/details/136672151

访问者模式

  • 一、介绍
  • 二、报表系统开发
  • 三、MyBatis中如何使用访问者模式?

一、介绍

Java 中的访问者(Visitor)模式是一种行为型设计模式,它将数据结构与数据操作分离,使得在不修改数据结构的情况下可以增加新的操作。该模式主要包含以下几个角色:

  1. 抽象访问者(Visitor): 定义了一个访问具体元素的接口,为每个具体元素类声明一个访问操作。
  2. 具体访问者(ConcreteVisitor): 实现了抽象访问者角色所声明的接口,定义了相应的访问操作。
  3. 抽象元素(Element): 声明一个接受访问操作的接口,这个接口的入口参数是抽象访问者角色。
  4. 具体元素(ConcreteElement): 实现了抽象元素角色所定义的接受访问操作的接口。
  5. 对象结构(ObjectStructure): 主要是用来存储元素对象的容器,提供让访问者对象遍历容器中所有元素的方法。

优点:

  • 符合单一职责原则,将数据结构与数据操作分离,提高了代码的可维护性。
  • 增加新的操作非常方便,只需要添加一个新的访问者即可,而不需要修改已有的数据结构代码。
  • 访问者模式使得数据结构对象的操作与维护相分离,符合开放-封闭原则。

缺点:

  • 增加新的数据结构类比较困难,因为每增加一个新的数据结构类,就需要修改所有的访问者实现。
  • 具体元素对访问者公布细节,会带来一些对象状态的透明性问题。
  • 访问者模式具有一定的复杂性,使用不当会增加系统的复杂度。

理解:

假设是一名老师,要去给不同的年级的学生上课。不同年级的学生,他们的学习能力不同,需要采取不同的教学方式。

  • 首先,学生就是我们的"元素(Element)“,不同年级的学生就是不同的"具体元素(ConcreteElement)”。
  • 然后,你作为老师就相当于一个"访问者(Visitor)“。不同的授课方式就是"具体访问者(ConcreteVisitor)”。

现在你要上课了,步骤如下:

  1. 你(访问者)进入一个教室(对象结构),里面坐着不同年级的学生们(元素们)。
  2. 你会观察一下教室里都有哪些年级的学生,比如一年级学生、二年级学生等等。
  3. 根据不同年级学生的情况,你会采取不同的授课方式。比如对一年级生,你会使用简单生动的教学方式;对二年级生,你就可以讲一些深入的知识了。

这里的关键点是:

  • 不同年级的学生(元素)并不知道你(访问者)会采取什么样的授课方式,它们只知道接受授课。
  • 而你作为老师(访问者),可以根据不同年级的学生采取不同的授课方式。

这样做的好处是什么呢?

  1. 新增一个年级的学生(元素)非常容易,只需要新增一个"具体元素"就行了,不需要修改之前所有的"访问者"。
  2. 如果要新增一种授课方式(访问者),你只需要新增一个"具体访问者"就行了,不需要修改所有的"元素"。

这样就可以很好地遵守"开闭原则",使得系统扩展相对容易,并提高代码的可维护性。

总的来说,访问者模式的核心思想就是:“将数据结构和作用于结构上的操作解耦,使得操作集合可相对自由地扩展”。这样不仅令系统数据结构的扩展更加灵活,也使给定的操作集更具统一性。

二、报表系统开发

在实际项目中,访问者模式常常被用于需要对一组异构元素执行不同操作的场景。下面是一个在报表系统中应用访问者模式的场景。

需求描述:我们需要开发一个报表系统,可以生成不同类型的报表,包括表格报表(TabularReport)和数据透视表报表(PivotTableReport)。每种报表都需要支持多种输出格式,如Excel、PDF和HTML。

使用访问者模式的优势:

  1. 报表类型和输出格式是相互独立的,我们可以很方便地添加新的报表类型或输出格式,而不需要修改现有代码。
  2. 报表生成逻辑与报表数据结构解耦,提高了代码的可维护性和可扩展性。
// 抽象访问者,定义访问表格报表和数据透视表报表的方法。
interface ReportVisitor {void visitTabularReport(TabularReport report);void visitPivotTableReport(PivotTableReport report);
}// 具体访问者 - Excel 输出
class ExcelVisitor implements ReportVisitor {@Overridepublic void visitTabularReport(TabularReport report) {// 生成 Excel 表格报表System.out.println("Generating Excel tabular report...");}@Overridepublic void visitPivotTableReport(PivotTableReport report) {// 生成 Excel 数据透视表报表System.out.println("Generating Excel pivot table report...");}
}// 具体访问者 - PDF 输出
class PdfVisitor implements ReportVisitor {// ...
}// 具体访问者 - HTML 输出
class HtmlVisitor implements ReportVisitor {// ...
}// 抽象元素
interface Report {void accept(ReportVisitor visitor);
}// 具体元素 - 表格报表
class TabularReport implements Report {private String data;public TabularReport(String data) {this.data = data;}@Overridepublic void accept(ReportVisitor visitor) {visitor.visitTabularReport(this);}// 其他方法...
}// 具体元素 - 数据透视表报表
class PivotTableReport implements Report {private String data;public PivotTableReport(String data) {this.data = data;}@Overridepublic void accept(ReportVisitor visitor) {visitor.visitPivotTableReport(this);}// 其他方法...
}// 客户端代码
public class Client {public static void main(String[] args) {List<Report> reports = new ArrayList<>();reports.add(new TabularReport("Tabular report data"));reports.add(new PivotTableReport("Pivot table report data"));ReportVisitor excelVisitor = new ExcelVisitor();for (Report report : reports) {report.accept(excelVisitor);}// 也可以使用其他访问者生成 PDF 或 HTML 报表}
}
  1. ReportVisitor 接口定义了访问表格报表和数据透视表报表的方法。
  2. ExcelVisitorPdfVisitorHtmlVisitor 是具体的访问者实现,分别用于生成不同格式的报表。
  3. Report 接口定义了接受访问者访问的方法 accept()
  4. TabularReportPivotTableReport 是两个具体的报表实现,它们实现了 accept() 方法,将自身作为参数传递给访问者的访问操作。
  5. 在客户端代码中,我们创建了一个报表列表,包含表格报表和数据透视表报表。然后创建了一个 Excel 访问者,并让它访问每个报表元素,生成相应的 Excel 报表。

通过使用访问者模式,我们可以很方便地添加新的报表类型或输出格式,而无需修改现有代码。例如,如果需要添加一种新的报表类型,只需创建一个新的具体报表类并实现 Report 接口即可。如果需要添加一种新的输出格式,只需创建一个新的具体访问者类并实现 ReportVisitor 接口即可。

三、MyBatis中如何使用访问者模式?

MyBatis 中,访问者模式被广泛地用于处理映射文件(Mapper XML)的解析和执行 SQL 查询操作。具体来说,MyBatis 使用了访问者模式来实现对 Mapper XML 文件中定义的不同元素(如 select、insert、update、delete 等)的解析和执行

在 MyBatis 中,访问者模式的使用主要集中在 org.apache.ibatis.parsing 包中,用于解析映射配置文件和动态 SQL 语句。我们重点分析 GenericTokenParser 类和相关组件。

1. 抽象访问者和抽象元素

在 MyBatis 中,抽象访问者和抽象元素分别定义在 TokenHandlerToken 接口中:

// 抽象访问者
public interface TokenHandler {String handleToken(String content);
}// 抽象元素
public interface Token {String getContent();void accept(TokenHandler handler);
}
  • TokenHandler 接口定义了访问者如何处理标记的方法。
  • Token 接口定义了元素如何接受访问者的访问操作。

2. 具体访问者和具体元素

MyBatis 提供了一些具体的访问者和元素实现,例如:

// 具体访问者 - 处理变量标记
public class VariableTokenHandler implements TokenHandler {private PropertyParser propertyParser;public VariableTokenHandler(Properties properties) {this.propertyParser = new PropertyParser(properties);}@Overridepublic String handleToken(String content) {return propertyParser.parse(content);}
}// 具体元素 - 变量标记
public class VariableToken implements Token {private String content;public VariableToken(String content) {this.content = content;}@Overridepublic String getContent() {return content;}@Overridepublic void accept(TokenHandler handler) {replaceBy(handler.handleToken(content));}// ...
}
  • VariableTokenHandler 是一个具体的访问者实现,用于处理变量标记。
  • VariableToken 是一个具体的元素实现,表示一个变量标记,它会将自身传递给访问者进行处理。

3. 使用访问者模式解析标记

GenericTokenParser 类中,MyBatis 使用访问者模式解析配置文件和动态 SQL 语句中的标记。以下是关键代码:

public class GenericTokenParser {private final String openToken;private final String closeToken;private final TokenHandler handler;// ...public String parse(String text) {// ...int start = text.indexOf(openToken);int end = text.indexOf(closeToken, start + openToken.length());if (start > -1 && end > start) {StringBuilder builder = new StringBuilder();// ...// 创建标记元素并让访问者处理它Token token = new VariableToken(text.substring(start + openToken.length(), end));token.accept(handler);builder.append(handler.handleToken(token.getContent()));// ...}// ...}
}

parse 方法中,MyBatis 会解析文本,识别出标记的起始和结束位置。然后,它会创建一个具体的标记元素(如 VariableToken)。接下来,它会让具体的访问者(如 VariableTokenHandler)访问和处理这个标记元素。

通过这种方式,MyBatis 将标记的解析操作和标记的数据结构分离,符合访问者模式的设计思想。

4. 扩展访问者和元素

由于 MyBatis 使用了访问者模式,因此扩展新的标记类型和处理逻辑变得非常方便。只需要实现新的具体访问者和具体元素,并在 GenericTokenParser 中进行调用即可。

例如,如果需要添加一种新的标记类型 MyToken,我们可以创建如下的具体访问者和具体元素:

// 具体访问者
public class MyTokenHandler implements TokenHandler {@Overridepublic String handleToken(String content) {// 处理 MyToken 的逻辑return "...";}
}// 具体元素
public class MyToken implements Token {private String content;public MyToken(String content) {this.content = content;}@Overridepublic String getContent() {return content;}@Overridepublic void accept(TokenHandler handler) {// 将自身传递给访问者if (handler instanceof MyTokenHandler) {replaceBy(handler.handleToken(content));}}
}

然后,在 GenericTokenParser 中添加相应的处理逻辑:

public String parse(String text) {// ...if (isMyToken(text)) {Token token = new MyToken(extractContent(text));token.accept(myTokenHandler);// ... 处理结果}// ...
}

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

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

相关文章

割点原理及封装好的割点类

作者推荐 视频算法专题 预备知识 本分析针对&#xff1a;连通无向图G。 搜索树 节点的父子关系&#xff1a;任意 节点的邻接 节点除了已处理 节点&#xff0c;都是它的子 节点。 以任意一点为根开始DFS&#xff0c;计算所有 节点的父子关系。只保留个子 节点到父 节点形成…

【实战项目】Boost搜索引擎项目

目录 1. 项目的相关背景 2. 搜索引擎的相关宏观原理 3. 搜索引擎技术栈和项目环境 4. 正排索引 vs 倒排索引 - 搜索引擎具体原理 4.1 正排索引 4.2 目标文档进行分词 4.3 倒排索引 4.4 模拟一次查找的过程&#xff1a; 5. 编写数据去标签与数据清洗的模块 Parser 5.1…

力扣串题:字符串中的第一个唯一字母

映射做法&#xff1a;将字母转为数字之类的转化必须在运算中实现如-a int firstUniqChar(char * s){int a[26] {0};int len strlen(s);int i;for (i 0; i < len; i)a[s[i] - a];for (i 0; i < len; i) {if (a[s[i] - a] 1)return i;}return -1; }

第42期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

Python学习笔记-Flask实现简单的投票程序

1.导入flask包 from flask import Flask,jsonify,abort,make_response,request,render_template 2.初始化 Flask 应用: app Flask(__name__) 3. 定义投票种类 data [{id:0,name:劳动节,num:0},{id:1,name:国庆节,num:0},{id:2,name:春节,num:0} ] 4.app.route(/index): …

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收OSD动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案的SDI接收G…

全国降雨侵蚀力因子R值/土壤侵蚀模型RUSLE

降雨侵蚀力因子其实是反应降雨对土壤侵蚀的潜在能力&#xff0c;就是降雨的冲刷对土壤的侵蚀效应。 在过去几天查阅文献资料的过程中&#xff0c;本人亲眼看见过的关于因子R的计算方法就超过30种&#xff0c;着实大开了眼界。 不过总结这些计算方法&#xff0c;其实核心思路大…

【相关问题解答1】bert中文文本摘要代码:import时无法找到包时,几个潜在的原因和解决方法

【相关问题解答1】bert中文文本摘要代码 写在最前面问题1问题描述一些建议import时无法找到包时&#xff0c;几个潜在的原因和解决方法1. 模块或包的命名冲突解决方法&#xff1a; 2. 错误的导入路径解决方法&#xff1a; 3. 第三方库的使用错误解决方法&#xff1a; 4. 包未正…

最新CLion + STM32 + CubeMX 开发环境搭建

网上有不少相关教程&#xff0c;但都是基于老版本Clion&#xff0c;新版有一些改变&#xff0c;但整体是简单了。 PS&#xff1a;本教程基于CLion 2023.3.4 安装所需工具参考&#xff1a;Clion搭建stm32开发环境&#xff08;STM32F103C8T6&#xff09;&#xff0c;有这一篇就够…

Java Day9 Stream流

Stream流 1、认识2、Stream流使用步骤3、如何获取Stream流4.Stream流的中间方法5、 Stream流终结方法 1、认识 2、Stream流使用步骤 3、如何获取Stream流 //list获取stream流List<String> listnew ArrayList<>();Collections.addAll(list,"崔十一","…

【数据结构与算法】解题20240313

这里写目录标题 一、现场写一个代码&#xff0c;有两个字符串类型的数字&#xff0c;实现一个方法将它们进行相加&#xff0c;并返回相加后的数值。&#xff08;要考虑数据的长度问题&#xff09;一、287. 寻找重复数三、617. 合并二叉树 一、现场写一个代码&#xff0c;有两个…

第10集《天台教观纲宗》

请大家打开讲义第十七页。我们讲到己二、结申正义。 己二、结申正义 《法华经》把我们修行人修行的相貌&#xff0c;比喻作一个车乘。车乘就是一种交通工具&#xff0c;它能够让我们从此岸超越到彼岸去。所以修行它是可以超越的&#xff0c;你今天比昨天超越了&#xff0c;就好…

【C++从练气到飞升】03---构造函数和析构函数

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 ⛳️推荐 一、类的6个默认成员函数 二、构造函数 1. 构造函数的概念 2. 构造函数的定义 3. 构造函数的特性 三、析构函…

opencv-python连通域分割connectedComponents

文章目录 连通域简介绘图代码函数说明 连通域简介 所谓连通域&#xff0c;即Connected Component&#xff0c;是一组彼此相连的像素点的集合&#xff0c;这些像素点彼此之间可以假设一条互相链接的路径&#xff0c;路径上所有像素的灰度一致&#xff0c;或者符合某个特定的条件…

Hackthebox - Scrambled- linux

Recon Port Scan HTTP 80 根据在 support 页面得到的信息&#xff1a; 邮箱 supportscramblecorp.com用户名 ksimpson一个用于连接 4411 端口的软件密码 ksimpson SMB 445 这里连接 SMB 服务是连不上的&#xff0c;因为禁用了 NTLM MSSQL 1443 SQL 服务也同样 Unkn…

MVCC原理

redo log 当没有redo log日志时 &#xff0c; 我们执行增删改语句之后会先更新Buffer Pool&#xff08;缓冲区&#xff09;&#xff0c;然后等待一定的数据后一起刷新回磁盘ibd&#xff0c;但是这个刷新过程中如果失败了&#xff0c;就会丢失数据&#xff0c;保证不了持久性 当…

编曲学习:钢琴编写 人性化、逻辑预制 工程音频导出

第8课 钢琴编写 人性化、逻辑预制 工程音频导出小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65e30339e4b064a8cfe56001?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 音乐创作中,有思路时可以不套学习到的公式,没有思路时可以套…

Kotlin 空类型,区间,数组

目录 1. 空类型 2. 区间 3. 数组 1. 空类型 我们知道任何一种数据类型都有为空或不为空两种状态&#xff0c;在 Kotlin 中&#xff0c;若允许一个数据为空&#xff0c;则需要使用 "?"&#xff0c;默认都不能为空。 代码举例说明 // 给 notNull赋值为空&#x…

Set cancelled by MemoryScratchSinkOperator

Bug信息 Caused by: com.starrocks.connector.spark.exception.StarrocksInternalException: StarRocks server StarRocks BE{host=10.9.14.39, port=9060} internal failed, status code [CANCELLED] error message is [Set cancelled by MemoryScratchSinkOperator]Bug产生的…

Qt 如何搭建Lua的运行环境

一、Lua简介 Lua 是一种强大的、高效的、轻量级的、可嵌入的脚本语言。它支持过程&#xff08;procedural&#xff09;编程、面向对象编程、函数式编程以及数据描述。Lua 是动态类型的&#xff0c;运行速度快&#xff0c;支持自动内存管理&#xff0c;因此被广泛用于配置、脚本…