首页 > 编程语言 >Java访问者模式源码剖析及使用场景

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

时间:2024-03-15 17:29:22浏览次数:19  
标签:Java String 元素 content 源码 访问者 public 报表

访问者模式

一、介绍

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 {
    @Override
    public void visitTabularReport(TabularReport report) {
        // 生成 Excel 表格报表
        System.out.println("Generating Excel tabular report...");
    }

    @Override
    public 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;
    }

    @Override
    public void accept(ReportVisitor visitor) {
        visitor.visitTabularReport(this);
    }

    // 其他方法...
}

// 具体元素 - 数据透视表报表
class PivotTableReport implements Report {
    private String data;

    public PivotTableReport(String data) {
        this.data = data;
    }

    @Override
    public 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);
    }

    @Override
    public String handleToken(String content) {
        return propertyParser.parse(content);
    }
}

// 具体元素 - 变量标记
public class VariableToken implements Token {
    private String content;

    public VariableToken(String content) {
        this.content = content;
    }

    @Override
    public String getContent() {
        return content;
    }

    @Override
    public 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 {
    @Override
    public String handleToken(String content) {
        // 处理 MyToken 的逻辑
        return "...";
    }
}

// 具体元素
public class MyToken implements Token {
    private String content;

    public MyToken(String content) {
        this.content = content;
    }

    @Override
    public String getContent() {
        return content;
    }

    @Override
    public 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);
        // ... 处理结果
    }
    // ...
}

标签:Java,String,元素,content,源码,访问者,public,报表
From: https://blog.csdn.net/weixin_44716935/article/details/136672151

相关文章

  • ThreadLocal源码解析
    ThreadLocalpublicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)//map不为null,之前设置过情况map.set(this,value);elsecreateMap(t,value);}privatevoidset......
  • java上传文件到FTP制定文件夹
    JAVA上传文件到FTP/***@ClassNameFTPLoad*@DescriptionTODO*@Authordell*@Date2024/3/1415:56*@Version1.0**/importcn.hutool.core.io.FileUtil;importcn.hutool.json.JSONObject;importcn.hutool.json.JSONUtil;importorg.apache.commons.net.......
  • javabean:VO和POJO的区别?
    实体类都是JavaBean的一种 实际上没区别 功能都一样 使用的时候区别(VO一般在命名结尾有大写VO 以做区别)参考:https://blog.csdn.net/huang_ftpjh/article/details/90232922关于java的几种对象(PO,VO,DAO,BO,POJO,DTO)解释摘抄参考2:https://blog.csdn.net/weixin_6938139......
  • idea开发java必备插件
    1.Lombok 这个大家都熟悉,通过注解的形式代替了很多生成式的代码,如Getter、Setter方法、ToString方法,构造函数等,使你的类更精简和美观,没有太多的冗余代码。2.Maven Helper,使用maven引入依赖的必备,分析和排除冲突依赖关系的简单方法,显示maven依赖树,查询引用的依赖关系以及跳转......
  • Java题目-数组计算-中位数- 圆类的构造-时间计算-学生类设计
    第一题:数组计算题目描述:编写Java程序,计算两个整型数组的和、差、乘积、商的整数部分及大小关系。定义如下:和:两个数组对应元素的和,若元素缺失,则补0;差:第一个数组和第二个数组对应元素的差,若元素缺失,则补0;乘积:两个数组对应元素的积,若元素缺失,则计0;除:第一个数组元素除以第二......
  • Mac下配置Java开发环境以Java 8 为例
    下面以Java8的安装和配置为例。Step1:下载Java安装包系统:MACOSX10.10.1Java8安装包:jdk-8u31-macosx-x64.dmgStep2:安装并配置Java环境:(1)打开Shell输入vi~/.bash_profile(2)i>输入>esc>:wq保存JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Conte......
  • Java-InputStream转Base64
    首先定义实体对象/***图片数据封装*/@DatapublicclassPgNewImageDto{/***文件类型*/privateStringfileType;/***文件数据*/privateStringfileData;}转换实现/***输入流转换为实体对象*@param......
  • 卡码java基础课 | 13.链表的基础操作I
    学习内容:链表基础重点归纳:见例题例题:解:点击查看代码importjava.util.Scanner;//定义链表classLinkedList{//定义链表中的链表节点publicstaticclassNode{intdata;//数据Nodenext;//指针publicNode(intdata){/......
  • [原创] KCP 源码分析(上)
    KCP协议是一种可靠的传输协议,对比TCP取消了累计确认(延迟ACK)、减小RTO增长速度、选择性重传而非全部重传。通过用流量换取低时延。KCP中最重要的两个数据结构IKCPCB和IKCPSEG,一个IKCPCB对应一个KCP连接,通过这个结构体维护发送缓存、接收缓存、超时重传时间、窗口大小等。I......
  • Java面向对象的一些学习笔记
    1.Private关键字:(1)private关键字是一个权限修饰符(2)可以修饰成员变量和成员方法(3)被private修饰的成员只能在本类中才能访问(4)针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作(5)提供"setXxx(参数)"方法,用于给成员变量赋值,方法用public修饰(6)提供"getXxx(参数)......