设计模式-访问者模式(Visitor)

1. 概念

  • 访问者模式(Visitor Pattern)是一种行为型设计模式。是一种将数据操作与数据结构分离的设计模式,其主要目的是将数据结构与数据操作解耦。

2. 原理结构图

图1
在这里插入图片描述

  • Visitor(访问者):接口或抽象类,它定义了对每个元素访问的行为。访问者接口中的方法数量理论上与元素的数量是一样的,因为它需要为每种类型的元素提供一个访问方法。这个角色是访问者模式的核心,它使得可以在不修改元素类的情况下增加新的操作。
  • ConcreteVisitor(具体访问者):实现Visitor接口的具体类,它实现了对每种元素的具体操作。
  • Element(元素):接口或抽象类,定义了一个接受访问者的接口,通常包含一个accept方法,该方法接受一个访问者对象作为参数。
  • ConcreteElement(具体元素):实现Element接口的具体类,它实现了接受访问者的方法,通常在accept方法中调用访问者的访问方法。
  • ObjectStructure(对象结构):这是包含各种元素的容器,如列表、集合或映射等。它需要提供迭代访问元素的方法,以便访问者可以访问这些元素。

3. 代码示例

3.1 示例1–图形结构
  • 有一个复杂的图形结构,其中包含不同类型的节点,如圆形、矩形和三角形。希望对这些节点执行不同的操作,如计算面积、周长和打印节点信息。
// 元素接口  
interface Shape {  void accept(ShapeVisitor visitor);  
}  // 具体元素类:圆形  
class Circle implements Shape {  private double radius;  public Circle(double radius) {  this.radius = radius;  }  @Override  public void accept(ShapeVisitor visitor) {  visitor.visit(this);  }  // 圆形特定的方法  public double getArea() {  return Math.PI * radius * radius;  }  public double getPerimeter() {  return 2 * Math.PI * radius;  }  
}  // 具体元素类:矩形  
class Rectangle implements Shape {  private double width;  private double height;  public Rectangle(double width, double height) {  this.width = width;  this.height = height;  }  @Override  public void accept(ShapeVisitor visitor) {  visitor.visit(this);  }  // 矩形特定的方法  public double getArea() {  return width * height;  }  public double getPerimeter() {  return 2 * (width + height);  }  
}  // 其他具体元素类(如三角形)可以类似地定义...// 访问者接口  
interface ShapeVisitor {  void visit(Circle circle);  void visit(Rectangle rectangle);  // 可以为其他形状添加更多的 visit 方法...  
}  // 具体访问者类:计算面积  
class AreaCalculator implements ShapeVisitor {  @Override  public void visit(Circle circle) {  System.out.println("Circle Area: " + circle.getArea());  }  @Override  public void visit(Rectangle rectangle) {  System.out.println("Rectangle Area: " + rectangle.getArea());  }  // 为其他形状实现的 visit 方法...  
}  // 具体访问者类:计算周长  
class PerimeterCalculator implements ShapeVisitor {  @Override  public void visit(Circle circle) {  System.out.println("Circle Perimeter: " + circle.getPerimeter());  }  @Override  public void visit(Rectangle rectangle) {  System.out.println("Rectangle Perimeter: " + rectangle.getPerimeter());  }  // 为其他形状实现的 visit 方法...  
}  // 其他具体访问者类(如打印信息)可以类似地定义...public class ShapeVisitorDemo {  public static void main(String[] args) {  // 创建形状对象  Shape circle = new Circle(5);  Shape rectangle = new Rectangle(4, 6);  // 创建访问者对象  ShapeVisitor areaCalculator = new AreaCalculator();  ShapeVisitor perimeterCalculator = new PerimeterCalculator();  // 使用访问者操作形状对象  circle.accept(areaCalculator); // 计算并打印圆形面积  rectangle.accept(areaCalculator); // 计算并打印矩形面积  circle.accept(perimeterCalculator); // 计算并打印圆形周长  rectangle.accept(perimeterCalculator); // 计算并打印矩形周长  // 可以继续添加更多的形状和访问者来执行复杂的操作...  }  
}
  • 输出
Circle Area: 78.53981633974483
Rectangle Area: 24.0
Circle Perimeter: 31.41592653589793
Rectangle Perimeter: 20.0
  • 在这个例子中,定义了一个Shape接口和它的两个实现类Circle和Rectangle。每个形状类都有一个accept方法,它接受一个ShapeVisitor类型的参数,并调用其对应的visit方法。为计算面积和周长分别定义了两个ShapeVisitor的实现类AreaCalculator和PerimeterCalculator。
    通过accept方法,可以将具体的形状对象与不同的访问者关联起来,执行所需的操作。这样,当需要添加新的操作或新的形状类型时,只需要添加新的访问者类或新的形状实现类,而无需修改现有的代码。这体现了访问者模式的开放封闭原则。

3.2 示例2–电商平台商品处理
  • 在电商平台商品处理的复杂场景中,可以使用访问者模式来处理商品的不同操作,例如计算总价、打印商品信息、应用促销折扣等。
// 商品接口  
interface Product {  void accept(ProductVisitor visitor);  double getPrice();  String getName();  
}  // 具体商品类:电子产品  
class ElectronicsProduct implements Product {  private String name;  private double price;  public ElectronicsProduct(String name, double price) {  this.name = name;  this.price = price;  }  @Override  public void accept(ProductVisitor visitor) {  visitor.visit(this);  }  @Override  public double getPrice() {  return price;  }  @Override  public String getName() {  return name;  }  
}  // 具体商品类:书籍  
class BookProduct implements Product {  private String name;  private double price;  public BookProduct(String name, double price) {  this.name = name;  this.price = price;  }  @Override  public void accept(ProductVisitor visitor) {  visitor.visit(this);  }  @Override  public double getPrice() {  return price;  }  @Override  public String getName() {  return name;  }  
}  // 其他商品类(如服装、食品等)可以类似地定义...// 访问者接口  
interface ProductVisitor {  void visit(ElectronicsProduct electronics);  void visit(BookProduct book);  // 可以为其他商品类型添加更多的 visit 方法...  
}  // 具体访问者类:计算总价  
class TotalPriceCalculator implements ProductVisitor {  private double total;  public double getTotal() {  return total;  }  @Override  public void visit(ElectronicsProduct electronics) {  total += electronics.getPrice();  }  @Override  public void visit(BookProduct book) {  total += book.getPrice();  }  // 为其他商品类型实现的 visit 方法...  
}  // 具体访问者类:打印商品信息  
class ProductInfoPrinter implements ProductVisitor {  @Override  public void visit(ElectronicsProduct electronics) {  System.out.println("Electronics: " + electronics.getName() + ", Price: $" + electronics.getPrice());  }  @Override  public void visit(BookProduct book) {  System.out.println("Book: " + book.getName() + ", Price: $" + book.getPrice());  }  // 为其他商品类型实现的 visit 方法...  
}  // 其他具体访问者类(如应用促销折扣)可以类似地定义...public class ProductVisitorDemo {  public static void main(String[] args) {  // 创建商品对象  Product electronics = new ElectronicsProduct("Laptop", 999.99);  Product book = new BookProduct("Java Programming", 49.95);  // 创建访问者对象  ProductVisitor totalPriceCalculator = new TotalPriceCalculator();  ProductVisitor productInfoPrinter = new ProductInfoPrinter();  // 使用访问者操作商品对象  electronics.accept(totalPriceCalculator);  book.accept(totalPriceCalculator);  System.out.println("Total Price: $" + totalPriceCalculator.getTotal());  electronics.accept(productInfoPrinter);  book.accept(productInfoPrinter);  // 可以继续添加更多的商品和访问者来执行复杂的操作...  }  
}
  • 将看到如下输出:
Total Price: $1049.94
Electronics: Laptop, Price: $999.99
Book: Java Programming, Price: $49.95
  • 在这个例子中,定义了一个Product接口和它的两个实现类ElectronicsProduct和BookProduct。每个商品类都有一个accept方法,它接受一个ProductVisitor类型的参数,并调用其对应的visit方法。为计算总价和打印商品信息分别定义了两个ProductVisitor的实现类TotalPriceCalculator和ProductInfoPrinter。通过accept方法,可以将具体的商品对象与不同的访问者关联起来,执行所需的操作。例如,可以计算商品的总价,也可以打印每个商品的详细信息。如果需要添加新的操作或新的商品

4. 优缺点

  • 主要作用
    • 访问者模式的主要作用是分离数据结构与数据操作,使得在不改变数据结构的情况下能够定义新的操作。
  • 优点
    • 高扩展性:能在不修改元素类结构的情况下增加新的操作,提高了系统的可扩展性。
    • 操作集中管理:将操作逻辑集中在访问者类中,便于管理和维护。
    • 灵活性增强:实现了数据与操作的解耦,使得数据结构更加稳定,操作更加灵活。
  • 缺点
    • 实现复杂:需要为每个元素类都实现接受和处理访问者的方法,这可能导致代码冗余。
    • 增加类数量:每个元素类都需要对应一个访问者类,可能导致系统中类的数量增加。
    • 破坏封装性:元素类需要暴露内部状态给访问者,可能违反封装原则。
    • 性能开销:元素类与访问者类之间的交互可能增加系统开销。

5. 应用场景

5.1 主要包括以下几个方面
  1. 数据结构稳定而操作易变:当数据结构相对稳定,但操作容易发生变化时,可以使用访问者模式将操作与数据结构分离,使得操作可以独立地变化和扩展,而不影响数据结构的稳定性。
  2. 需要为多种对象结构提供统一操作:当需要对多种不同的对象结构执行相同的操作时,可以使用访问者模式定义一个统一的访问者接口,使得不同的对象结构都可以接受相同的访问者进行访问和操作。
  3. 复杂的对象结构:对于复杂的对象结构,如树形结构或图结构,如果需要对结构中的每个元素都进行某些处理,访问者模式可以很好地遍历整个结构并对每个节点进行统一的操作。

5.2 实际应用
  1. 博物馆导览系统:在博物馆中,导览员作为访问者,可以访问不同的展品对象(如绘画、雕塑等)。根据游客的兴趣和需求,导览员可以为他们提供不同的讲解和信息,而无需修改展品本身。
  2. 电商平台商品处理:在电商平台上,商品分类可以被看作是访问者,它能够访问并处理不同类型的商品对象(如服装、数码产品等)。根据业务需求,商品分类可以按照价格、品牌等方式对商品进行排序、筛选等操作。
  3. 编译器设计:在编译器中,语法树的结构相对固定,但针对不同的语法可能需要执行不同的操作(如语法检查、代码优化等)。通过访问者模式,可以定义不同的访问者类来访问和处理语法树节点,从而实现灵活的编译过程。
  4. 图形界面编辑:在图形界面编辑软件中,不同的图形对象(如矩形、圆形等)可能具有不同的属性和操作。通过使用访问者模式,可以定义统一的访问者接口来访问和操作这些图形对象,实现灵活的图形编辑功能。

6. JDK中的使用

java.nio.file包中的文件树遍历

  • Visitor(访问者)
    • FileVisitor接口:这是一个访问者接口,它包含了四个方法,分别对应访问前、访问后、访问失败和访问文件时的动作。
    • SimpleFileVisitor类:这是一个简单的访问者实现,用户可以通过继承这个类并重写自己关心的方法来定制自己的访问者行为。
  • ConcreteVisitor(具体访问者)
    • 用户自定义的FileVisitor实现:用户可以创建一个类,实现FileVisitor接口,提供对文件或目录访问前、访问后以及遇到错误时的具体处理逻辑。
  • Element(元素)
    • Path接口:代表文件系统中的路径,是文件树结构的基本元素。
  • ConcreteElement(具体元素)
    • 各种类型的Path对象:如DirectoryNode和FileNode,它们分别表示文件系统中的目录节点和文件节点。
  • ObjectStructure(对象结构)
    • Files.walkFileTree方法:这个方法接受一个起始Path和一个FileVisitor对象作为参数,它会遍历从给定的Path开始的文件树,使用提供的FileVisitor来处理每个访问到的元素。

通过使用访问者模式,JDK提供了一种灵活的方式来处理文件系统中的文件和目录。用户可以定义自己的FileVisitor实现,而不需要修改Files类的代码,这有助于保持核心API的稳定性。同时,这种模式也使得添加新的操作变得简单,因为只需添加一个新的访问者实现即可,无需改动现有的元素类或对象结构。


7. 注意事项

  • 复杂性管理:访问者模式可能会增加系统的复杂性,特别是当元素和访问者类型众多时。确保使用此模式确实带来了所需的灵活性,并仔细考虑维护成本。
  • 扩展性考虑:虽然访问者模式易于为元素添加新操作,但为访问者添加新元素类型可能需要修改所有访问者类。在设计时,要预见可能的扩展需求。
  • 数据访问封装:访问者模式通常用于封装对数据结构内部元素的访问和操作。确保访问者的设计不会暴露不必要的内部状态,以保持封装性。
  • 性能考虑:访问者模式可能涉及多次遍历数据结构,特别是在处理大型数据集时。要评估性能影响,并在必要时进行优化。
  • 双重分派:访问者模式依赖于双重分派机制(运行时确定操作),这在某些语言或环境下可能不是最高效的。理解并接受这种机制的性能特性是很重要的。
  • 设计模式选择:确保访问者模式是解决问题的最佳方案。有时,其他设计模式可能更适合特定的需求。

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

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

相关文章

47.基于SpringBoot + Vue实现的前后端分离-校园外卖服务系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统,采用SpringBoot Vue框架,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得基于SpringBoot Vue技术的校园外卖服务系统设计与实现管理工作…

【前端Vue】Vue从0基础完整教程第7篇:组件化开发,组件通信【附代码文档】

Vue从0基础到大神学习完整教程完整教程(附代码资料)主要内容讲述:vue基本概念,vue-cli的使用,vue的插值表达式,{{ gaga }},{{ if (obj.age > 18 ) { } }},vue指令,综合…

Python基于Django搜索的目标站点内容监测系统设计,附源码

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

全国产化无风扇嵌入式车载电脑在车队管理嵌入式车载行业应用

车队管理嵌入式车载行业应用 车队管理方案能有效解决车辆繁多管理困难问题,配合调度系统让命令更加精确有效执行。实时监控车辆状况、行驶路线和位置,指导驾驶员安全有序行驶,有效降低保险成本、事故概率以及轮胎和零部件的磨损与损坏。 方…

LeetCode刷题总结 | 图论3—并查集

并查集理论基础 1.背景 首先要知道并查集可以解决什么问题呢? 并查集常用来解决连通性问题。大白话就是当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。 并查集主要有两个功能: 将两个元素添加到一个集合中。判…

安装GPT 学术优化 (GPT Academic)@FreeBSD

GPT 学术优化 (GPT Academic)是一个非常棒的项目 可以帮助我们完成中科院的一些日常工作。 官网:GitHub - binary-husky/gpt_academic: 为GPT/GLM等LLM大语言模型提供实用化交互接口,特别优化论文阅读/润色/写作体验,模块化设计,…

【Linux系列】Ctrl + R 的使用

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

EelasticSearch的docker安装-----》es客户端使用!!!

1.Docker安装 docker run -d --name es7 -e ES_JAVA_POTS"-Xms256m -Xmx256m" -e "discovery.typesingle-node" -v /opt/es7/data/:/usr/share/elasticsearch/data -p 9200:9200 -p 9300:9300 elasticsearch:7.14.02.客户端UI工具,Edge浏览器…

Linux(磁盘管理与文件系统)

目录 1. 磁盘基础 1.1 磁盘结构 1.2 MBR 1.3 磁盘分区结构 2. 文件系统类型 2.1 XFS文件系统 2.2 SWAP 2.3 fdisk命令 2.4 创建新硬盘 3.创建文件系统 3.1 mkfs 3.2 挂载、卸载文件系统 3.3 查看磁盘使用情况 1. 磁盘基础 1.1 磁盘结构 磁盘的物理结构 盘片:硬…

35. UE5 RPG制作火球术技能

接下来,我们将制作技能了,总算迈进了一大步。首先回顾一下之前是如何实现技能触发的,然后再进入正题。 如果想实现我之前的触发方式的,请看此栏目的31-33篇文章,讲解了实现逻辑,这里总结一下: …

微服务拆分:打造高性能、高扩展的未来架构

目录 一、微服务介绍 二、主链路规划 2.1 业务完整性 2.2 转化率重因子 2.3 流量端占比 2.4 现金水库 三、如何识别主链路 3.1 导流端 3.2 转化端 3.3 漏斗中部:订单转化 3.4 漏斗底部:下单 四、总结 一、微服务介绍 单体应用将所有的功能都…

微服务架构与Dubbo

一、微服务架构 微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。 分布式系统式若干独立系统的集合,但是用户使用起来好像是在使用一套系统。 和微服务对应的是单体式开发,即所有的功能打包在一个WAR…

搭建Zookeeper完全分布式集群(CentOS 9 )

ZooKeeper是一个开源的分布式协调服务,它为分布式应用提供了高效且可靠的分布式协调服务,并且是分布式应用保证数据一致性的解决方案。该项目由雅虎公司创建,是Google Chubby的开源实现。 分布式应用可以基于ZooKeeper实现诸如数据发布/订阅…

Vue的生命周期的详解

Vue的生命周期是每个使用Vue框架的前端人员都需要掌握的知识,以此作为记录。 Vue的生命周期就是vue实例从创建到销毁的全过程,也就是new Vue() 开始就是vue生命周期的开始。Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译…

算法|最大堆、最小堆和堆排序的实现(JavaScript)

一些概念 堆&#xff1a;特殊的完全二叉树&#xff0c;具有特定性质的完全二叉树。大根堆&#xff1a;父节点 > 子节点小根堆&#xff1a;父节点 < 子节点 二叉堆也属于完全二叉树&#xff0c;所以可以用数组表示。 若下标从1开始&#xff0c;左节点为 2*i &#xff0…

树莓派4B+Debian(Raspbian)+开机自启动Python文件

非系统全面的教程&#xff0c;只是记录操作过程中的问题和解决方案。 说明1&#xff1a;树莓派的不同版本&#xff0c;要查看CPU的位数、内存大小&#xff1b;从而确定安装的raspbian的对应版本 若是对应64位的OS&#xff0c;安装成了32位的OS&#xff0c;可能会对系统中安装…

5、JVM-G1详解

G1收集器 -XX:UseG1GC G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. G1将Java堆划分为多个大小相等的独立区域&#xff08;Region&#xff09;&#xff0c;JVM目标…

代码学习记录25---单调栈

随想录日记part45 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.04.17 主要内容&#xff1a;今天开始要学习单调栈的相关知识了&#xff0c;今天的内容主要涉及&#xff1a;每日温度 &#xff1b;下一个更大元素 I 739. 每日温度 496.下一个更大元素 I Topic…

书生·浦语大模型全链路开源体系-第4课

书生浦语大模型全链路开源体系-第4课 书生浦语大模型全链路开源体系-第4课相关资源XTuner 微调 LLMXTuner 微调小助手认知环境安装前期准备启动微调模型格式转换模型合并微调结果验证 将认知助手上传至OpenXLab将认知助手应用部署到OpenXLab使用XTuner微调多模态LLM前期准备启动…

Mac电脑上有什么好玩的格斗游戏 《真人快打1》可以在苹果电脑上玩吗

你是不是喜欢玩格斗游戏&#xff1f;你是不是想在你的Mac电脑上体验一些刺激和激烈的对战&#xff1f;在这篇文章中&#xff0c;我们将介绍Mac电脑上有什么好玩的格斗游戏&#xff0c;以及《真人快打1》可以在苹果电脑上玩吗。 一、Mac电脑上有什么好玩的格斗游戏 格斗游戏是…