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

访问者模式

  • 一、介绍
  • 二、报表系统开发
  • 三、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.mzph.cn/news/741457.shtml

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

相关文章

【Linux】Centos7上安装MySQL5.7

目录 1.下载安装包2. 上传安装包3.将 mysql 解压到/usr/local/4.重命名5.创建mysql用户及用户组6. 进入 mysql 目录修改权限7. 安装依赖库8. 执行安装脚本9. 复制启动脚本到资源目录10. 拷贝 my.cnf&#xff0c;并赋予权限11. 配置环境变量12. 启动 mysqld13. 登录 MySQL&#…

3.13练习题解

1.空调&#xff1a; 这道题目我们不妨直接考虑&#xff08;应该也很容易想到是差分&#xff0c;因为题目中给出的空调的功能已经有提示了&#xff09;&#xff0c;那么我们不妨对问题进行一下转化&#xff1a; 首先将当前温度和目标温度都确定下来&#xff0c;也就是&#xff…

1361:产生数(Produce)

【解题思路】 1、将数字拆分保存在数组中&#xff0c;而后转换每一位。 2、将数字变化规则保存在x、y两个一维数组中&#xff0c;x[i]到y[i]是一种转换规则。 3、从n的初始值开始搜索&#xff0c;对n做数字拆分&#xff0c;将拆分后的各位数字保存在一个数组中。针对数组中的每…

初识进程状态

&#x1f30e;进程状态【上】 文章目录&#xff1a; 进程状态 发现进程的状态 运行队列 进程排队 进程状态的表述       状态在代码中的表示       运行状态       阻塞状态       挂起状态 总结 前言&#xff1a; 为了搞明白正在运行的进程是什么意思…

“批量记录,轻松修改:让收支明细管理更高效!“

在繁忙的现代生活中&#xff0c;管理个人收支明细成为了我们理财的重要一环。晨曦记账本&#xff0c;作为一款功能强大的记账工具&#xff0c;致力于帮助用户轻松记录和管理每一笔收支&#xff0c;让财务更加清晰、有序。 第一步&#xff0c;首先我们要记进入晨曦记账本主页面…

TSN工业交换机在煤矿行业的革命性应用

随着信息化时代的到来&#xff0c;煤矿行业也迎来了前所未有的机遇与挑战。在这个充满活力和竞争的领域&#xff0c;技术的革新对于提高生产效率、保障安全生产至关重要。而随着时间敏感网络&#xff08;TSN&#xff09;技术的不断发展&#xff0c;TSN工业交换机作为其关键组成…

第二证券|股票开户有什么条件?炒股新手一定要看!

股票买卖尽管投资风险是很高的&#xff0c;可是由于其报答性也高&#xff0c;仍是有许多投资者乐意去测验&#xff0c;这也带动了许多新手买卖。那么股票开户有什么条件&#xff1f;下面就由第二证券为大家剖析&#xff1a; 股票开户有什么条件&#xff1f; 1、年龄要求 处理…

Anaconda3安装pandas失败,处理办法

安装 pandas 失败可能是由于多种原因造成的&#xff0c;以下是一些解决这个问题的常见方法&#xff1a; 0、清理Anaconda缓存,重新安装pandas conda clean --all conda install pandas 1、更新 Anaconda&#xff1a; 确保你的 Anaconda 环境是最新的。可以使用以下命令来更新…

C# mysql数据库操作

MySQL 是一个流行的开源关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发并后来被 Oracle 公司收购。MySQL 提供了可靠和高性能的数据存储和检索能力&#xff0c;适用于各种规模的应用程序。以下是 MySQL 数据库的介绍、使用场景以及优缺点&#xff1a; 数据库介绍&…

二分查找的理解及应用场景。

一、是什么 在计算机科学中&#xff0c;二分查找算法&#xff0c;也称折半搜索算法&#xff0c;是一种在有序数组中查找某一特定元素的搜索算法 想要应用二分查找法&#xff0c;则这一堆数应有如下特性&#xff1a; 存储在数组中有序排序 搜索过程从数组的中间元素开始&…

(黑马出品_高级篇_02)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

&#xff08;黑马出品_高级篇_02&#xff09;SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术——分布式事务 今日目标1.分布式事务问题1.1.本地事务1.2.分布式事务1.3.演示分布式事务问题 2.理论基础2.1.CAP定理2.1.1.一致性2.1.2.可用性2…

WMS系统批次管理全面解析

一、WMS系统批次管理的基本概念 WMS系统的批次管理&#xff0c;是指对仓库中不同批次的产品进行精细化、规范化的管理&#xff0c;确保产品的可追溯性、可控制性和安全性。批次管理涉及到产品的入库、存储、出库等各个环节&#xff0c;通过对批次的精确控制&#xff0c;实现仓…

面试经典-4-LRU 缓存

题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则返…

RK3568行业定制主板脉冲群抗扰度整改方案验证

1. 试验目的及原理 电快速瞬变脉冲群EFT试验的目的是验证电子设备机械开关对电感性负载切换、继电器触点弹跳、高压开关切换等引起的瞬时扰动的抗干扰能力。这种试验方法是一种耦合到电源线路、控制线路、信号线路上的由许多快速瞬变脉冲组成的脉冲群试验。容易出现问题的场合有…

微信小程序开发:上传网络图片到阿里云oss

上文遇到的问题&#xff0c;用户上传的人像图片在经过人像增强后返回的结果需要再次上传到阿里云的oss。 因为是需要下下载&#xff0c;再上传&#xff0c;这个域名我们没有在MP后台配置download域名&#xff0c;所以报错了&#xff1a; 但是MP后台只能配置https的域名&#xf…

常见面试题之计算机网络

1. OSI 五层模型&#xff08;或七层模型&#xff09;是什么&#xff0c;每一层的作用是什么 应用层&#xff1a;又可细分为应用层、表示层、会话层。其中应用层主要做的工作就是为应用程序提供服务&#xff0c;常见的协议为 HTTP、HTTPS、DNS等&#xff1b;表示层主要做的工作…

如何只用pycharm创建venv虚拟环境

如何只用pycharm创建venv虚拟环境 网上貌似没有好的博客讲解&#xff0c;我之前也一直在这环境搭建上疑惑。不过照着我这博客来&#xff0c;应该没问题了。 前言 如图所示&#xff0c;我新建了一个空白文件夹。 接下来点击终端&#xff08;AltF12&#xff09; 如果发现开头…

使用Thymeleaf-没有js的html模板导出为pdf

html模板 <!DOCTYPE html> <html xmlns:th"http://www.thymeleaf.org"><head><title>PDF Template</title> </head> <body> <h1>User Information</h1> <p>Name: <span th:text"${user.name}&…

【BI-Dataease】dataease设计思路

参考&#xff1a;https://juejin.cn/post/7089310768671227940 1、BI可视化工具的关键问题是什么&#xff1f; &#xff08;1&#xff09;各种数据源的数据结构和类型如何统一&#xff1f; &#xff08;2&#xff09;各种图表的配置属性不一致&#xff0c;属性如何绑定到数据…

不同系统有着不同的文件路径分隔符

在不同的操作系统中&#xff0c;文件路径的分隔符是不一样的&#xff1a; 在类 Unix&#xff08;例如 Linux 和 macOS&#xff09;系统中&#xff0c;文件路径使用正斜杠&#xff08;/&#xff09;作为目录分隔符。在 Windows 系统中&#xff0c;文件路径使用反斜杠&#xff0…