设计模式-访问者(Visitor)模式详解和应用

文章目录

  • 前言
  • 访问者模式介绍
  • 访问者模式优缺点
  • 访问者模式包含的主要角色
  • 应用场景
  • 代码示例
  • 访问者模式的扩展
  • 总结


前言

最近在做一个根据数学表达式生成java执行代码的功能,其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来,不足之处请大家指正。


访问者模式介绍

访问者模式(Visitor Pattern)是GoF提出的23种设计模式中的一种,属于行为模式。GoF《Design Pattern》中的定义 :表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

访问者模式优缺点

访问者模式是一种将数据操作与数据结构分离的设计模式,它使得你可以在不修改已有类的情况下增加新的操作。以下是访问者模式的优点和缺点:

优点:

  1. 扩展性好:访问者模式使得增加新的操作变得容易。当需要增加新的操作时,只需要增加新的访问者类即可,无需修改已有的元素类。
  2. 符合单一职责原则:访问者模式可以将原本分散在各个元素类中的操作集中起来,使得每个类的职责更加单一。
  3. 灵活性高:访问者模式可以灵活地访问和操作对象结构中的元素,通过访问者接口可以定义多种不同的访问和操作方式。
  4. 双层分离:访问者模式将数据与数据结构分离,使得两者可以独立变化,降低了系统的耦合度。

缺点:

  1. 增加系统复杂性:访问者模式增加了新的抽象层次,可能导致系统变得更加复杂。如果系统本身并不复杂,或者不需要经常增加新的操作,使用访问者模式可能会引入不必要的复杂性。
  2. 破坏封装性:访问者模式要求被访问的对象暴露一些方法和数据,这可能会破坏对象的封装性。
  3. 增加新的元素变更困难:对于访问者而言,增加新的元素类很困难,因为访问者类针对每一个元素类都提供访问操作,因此在系统中增加新的元素类,就必须修改所有访问者类,增加新的操作。这违背了“开闭原则”。
  4. 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

总的来说,访问者模式在某些场景下非常有用,但在选择是否使用它时,需要权衡其优缺点,并结合具体的应用场景来决定。

访问者模式包含的主要角色

  • 抽象访问者(Visitor)。定义了访问者可以访问的元素对象的接口,包含多个 visit() 方法,每个方法对应一个具体元素对象。123
  • 具体访问者(ConcreteVisitor)。实现了抽象访问者接口,对不同类型的元素对象进行具体的操作。
  • 抽象元素(Element)。定义了接受访问者accept方法,不同的具体元素可以实现该方法以不同的方式响应访问者的访问。
  • 具体元素(ConcreteElement)。实现了抽象元素中定义的接受访问者访问的方法,并在其中调用访问者的访问方法。
  • 对象结构(Object Structure)。定义了具体元素的集合,并提供了遍历集合中元素的方法。

应用场景

  1. 电商网站的商品分类与操作

    • 电商网站通常有大量的商品,这些商品可以按照不同的属性进行分类,如数码产品、服装鞋帽、家居用品等。
    • 使用访问者模式,可以定义一个访问者对象,它能够访问不同类型的商品对象,并根据需要执行不同的操作,如按照价格排序、根据品牌筛选等。
  2. 图形编辑器的操作处理

    • 在一个复杂的图形编辑器中,有多种图形元素,如线条、圆形、矩形等。
    • 访问者模式允许定义一个访问者对象,它能够访问这些图形元素并执行不同的操作,如移动、缩放、旋转或改变颜色等。
  3. 编译器和解释器设计

    • 在编译器或解释器的设计中,抽象语法树(AST)是一个常见的数据结构,它包含了程序的各个部分。
    • 使用访问者模式,可以为AST中的不同节点类型定义不同的访问者,以执行如类型检查、代码优化、代码生成等操作。

代码示例

将Visitor设计模式与Shape类结合使用,你可以定义一个Shape接口和多个具体的Shape实现类(如Circle、Rectangle、Triangle等),并为这些Shape对象实现不同的操作。

以下是一个简单的示例,展示了如何使用Visitor设计模式对Shape对象进行操作:

// Shape 接口
interface Shape {  void accept(ShapeOperation operation);  
}  // Circle 类,实现了 Shape 接口  
class Circle implements Shape {  private double radius;  public Circle(double radius) {  this.radius = radius;  }  public double getRadius() {  return radius;  }  @Override  public void accept(ShapeOperation operation) {  operation.visit(this);  }  
}  // Square 类,实现了 Shape 接口  
class Square implements Shape {  private double side;  public Square(double side) {  this.side = side;  }  public double getSide() {  return side;  }  @Override  public void accept(ShapeOperation operation) {  operation.visit(this);  }  
}

定义一个ShapeVisitor接口和具体的Visitor实现类:


// ShapeVisitor 接口
interface ShapeVisitor {  void visit(Circle circle);  void visit(Square square);  
}  // PrintAreaVisitor 类,实现了 ShapeOperation 接口  
class PrintAreaVisitor implements ShapeVisitor {  @Override  public void visit(Circle circle) {  double area = Math.PI * circle.getRadius() * circle.getRadius();  System.out.println("Circle area: " + area);  }  @Override  public void visit(Square square) {  double area = square.getSide() * square.getSide();  System.out.println("Square area: " + area);  }  
}  // PrintPerimeterVisitor 类,实现了 ShapeOperation 接口  
class PrintPerimeterVisitor implements ShapeVisitor {  @Override  public void visit(Circle circle) {  double perimeter = 2 * Math.PI * circle.getRadius();  System.out.println("Circle perimeter: " + perimeter);  }  @Override  public void visit(Square square) {  double perimeter = 4 * square.getSide();  System.out.println("Square perimeter: " + perimeter);  }  
}

使用Visitor对Shape对象进行操作:

public class ShapeClient {public static void main(String[] args) {  Shape circle = new Circle(5);  Shape square = new Square(10);  // 使用 PrintAreaVisitor 来打印面积  ShapeVisitor areaVisitor = new PrintAreaVisitor();  circle.accept(areaVisitor);  square.accept(areaVisitor);  // 使用 PrintPerimeterVisitor 来打印周长  ShapeVisitor perimeterVisitor = new PrintPerimeterVisitor();  circle.accept(perimeterVisitor);  square.accept(perimeterVisitor);  }  
}

访问者模式的扩展

访问者(Visitor)模式常常同以下设计模式联用:

  • 与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如上面示例代码中的对象结构是用 List 实现的,它通过 List 对象的 Iterator() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。

  • 访问者(Visitor)模式与“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。

  • 与模板方法模式联用:可以在保持算法骨架不变的情况下,通过访问者模式为不同的对象结构添加新的操作。具体来说,可以将模板方法模式中定义的一系列步骤作为访问者接口中的方法,然后在具体的访问者实现中定义这些步骤的具体行为。这样,就可以通过创建不同的访问者来实现对同一对象结构的不同操作。
    假设我们有一个系统需要处理不同类型的文件(如文本文件、图像文件等),每种文件类型都有一系列的处理步骤(如打开、读取、处理、关闭等)。我们可以使用模板方法模式来定义这些处理步骤的算法骨架,并使用访问者模式来为每个文件类型定义具体的处理逻辑。
    首先,我们定义一个处理文件的算法骨架(模板方法模式):

public abstract class FileProcessor {  public final void processFile(File file) {  openFile(file);  readFile(file);  handleFile(file); // 这是一个钩子方法,可以在子类中覆盖  closeFile(file);  }  protected abstract void openFile(File file);  protected abstract void readFile(File file);  protected void handleFile(File file) {  // 默认实现,可以在子类中覆盖  }  protected abstract void closeFile(File file);  
}

然后,我们定义访问者接口,该接口包含与文件处理步骤相对应的方法:

public interface FileVisitor {  void visitOpenFile(File file);  void visitReadFile(File file);  void visitHandleFile(File file);  void visitCloseFile(File file);  
}

接着,我们为每种文件类型创建具体的访问者实现:

public class TextFileVisitor implements FileVisitor {  @Override  public void visitOpenFile(File file) {  // 文本文件特定的打开逻辑  }  @Override  public void visitReadFile(File file) {  // 文本文件特定的读取逻辑  }  @Override  public void visitHandleFile(File file) {  // 文本文件特定的处理逻辑  }  @Override  public void visitCloseFile(File file) {  // 文本文件特定的关闭逻辑  }  
}  // 类似地,可以为图像文件等创建其他的FileVisitor实现...

最后,我们创建具体的文件处理器类,并将访问者注入其中:

public class TextFileProcessor extends FileProcessor {  private FileVisitor visitor;  public TextFileProcessor(FileVisitor visitor) {  this.visitor = visitor;  }  @Override  protected void openFile(File file) {  visitor.visitOpenFile(file);  }  @Override  protected void readFile(File file) {  visitor.visitReadFile(file);  }  @Override  protected void handleFile(File file) {  visitor.visitHandleFile(file);  }  @Override  protected void closeFile(File file) {  visitor.visitCloseFile(file);  }  
}

总结

访问者模式主要用于将操作与数据结构分离,增加新的操作变得简单,但增加新的元素类可能会导致修改访问者接口和所有的具体访问者类。以下情况可以考虑使用访问者模式:

  1. 对象结构稳定但操作易变:当有一个对象结构(如集合、数组等),其包含的元素类型比较稳定,但针对这些元素的操作经常发生变化时,Visitor模式非常适用。使用Visitor模式,你可以在不修改对象结构的情况下,通过添加新的访问者类来实现新的操作。

  2. 避免使用多重继承:在一些编程语言中,多重继承可能会引入复杂性、潜在的冲突和不易维护的代码。使用Visitor模式,你可以将操作逻辑封装在访问者类中,从而避免使用多重继承。

  3. 跨多个类进行类似操作:当需要在多个不相关的类中执行类似的操作时,Visitor模式可以帮助你将这些操作集中管理。通过将操作逻辑放在访问者类中,你可以减少代码重复,提高代码的可维护性。

  4. 操作需要访问对象内部状态:如果操作需要访问对象的内部状态,并且这些状态对于操作来说是透明的,那么使用Visitor模式可以将操作与对象状态分离。这样,你可以在不修改对象类的情况下,通过修改访问者类来改变操作的行为。

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

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

相关文章

生命源集团2024全球品牌发布会成功举办

生命源集团2024全球品牌发布会圆满落幕 3月20日,生命源集团在杭州隆重举办了主题为“生命源启,荣耀之巅”的2024全球品牌发布会。 活动伊始,嘉宾们陆续签到入场,现场气氛热烈而庄重。随后,生命源集团十二大事业部总裁…

6.2 ServiceNow 自动化测试框架 (ATF)

6.2 自动化测试框架 ATF 目录一、自动化测试框架 (ATF) 简介1. Automated Test Framework(ATF)2. 使用自动化测试框架 (ATF)的好处: 二、 ATF的测试类型1. 功能业务逻辑测试2. 回归测试3. 浏览器兼容性测试4. 服务器端 Jasmine测试 三、 ATF测…

详解:创业老阳推荐的Temu蓝海项目还能赚钱吗?

在当前全球化的背景下,跨境电商行业日益繁荣,成为了许多创业者关注的焦点。其中,Temu项目凭借其独特的商业模式和强大的市场潜力,备受瞩目。尤其是当知名创业导师老阳推荐Temu项目时,更是激起了广大创业者的热情和好奇…

机器人路径规划:基于冠豪猪优化算法(Crested Porcupine Optimizer,CPO)的机器人路径规划(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人(Mobile robot,MR)的路径规划是 移动机器人研究的重要分支之,是对其进行控制的基础。根据环境信息的已知程度不同,路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

应急响应-Linux(1)

应急响应-Linux(1) 黑客的IP地址 思路: 一般系统中马之后会有进程连接黑客的主机,可以使用netstat -anpt查看下当前进程的连接,此处查看到没有后 ,可以从系统服务开始查找,系统的服务日志一般都会保存相关访问信息&…

SAP CAP篇十五:写个ERP的会计系统吧,Part II

本文目录 本系列文章目标开发步骤数据库表设计初始数据初始数据:AccountCategories初始数据:AccountUsages初始数据:ChartOfAccounts初始数据:AccountSubjects Service 定义生成Fiori AppApp运行 本系列文章 SAP CAP篇一: 快速创…

P8597 [蓝桥杯 2013 省 B] 翻硬币 Python

[蓝桥杯 2013 省 B] 翻硬币 题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果…

mysql 学习

本文来自于《sql必知必会》 所需要的文件教程连接 本站其他的小伙伴 第一课 了解sql 数据库基础 什么是数据库 数据库(database) 保存有组织的数据的容器(通常是一个文 件或一组文件)。 表 表(table)…

npm常用命令详解

npm(Node Package Manager)是 Node.js 的包管理器,用于管理项目中的依赖(libraries, frameworks, tools)。以下是一些 npm 的常用命令及其详解: 1. npm init 作用:初始化一个新的 Node.js 项目…

MQTT Keep Alive机制

MQTT 协议是承载于 TCP 协议之上的, 而 TCP 协议以连接为导向, 在连接双方之间, 提供稳定、 有序的字节流功能。 但是, 在部分情况下, TCP 可能出现半连接问题。 所谓半连接, 是指某一方的连接已经断开或者…

【ppt技巧】PPT转换为图片,方法有哪些?

想要将ppt文件转换为图片,其实很简单,一起来看一下如何操作吧! 方法一: 使用格式转换器,有些文件格式转换器,支持ppt转换为图片。 方法二: 不需要转换器,直接在ppt中进行操作即可…

Java基础知识总结(9)

快速排序 3 1 2 5 4 6 重复第一轮的过程,应该得到如下序列: 2 1 3 5 4 6 OK,现在3已经归位。接下来需要处理3左边的序列: 2 1 3 6 处理之后,2已经归位,序列“1”只有一个数,也不需要进行任何…

可观测性体系建设后,该如何挖掘数据及工具价值?

在现代企业的运维管理中,构建高效且可靠的可观测性体系是保障系统稳定性和业务连续性的关键。然而,运维团队成员的技术能力参差不齐往往成为实现这一目标的障碍。尤其在处理复杂系统故障时,高度依赖专业知识和经验的可观测性工具很难被全员有…

j-vxe-table设置

1.设置按键回车箭头tab健设置 :mouse-config"{selected: true}" :keyboard-config"{ isArrow: true, isEnter: true, isEdit: true,isTab:true}" 2 表格编辑设置 :edit-config"{trigger: this.triggerFlag, mode: row, showIcon: false , active…

Java 基础 反射

什么是反射? 反射是各类框架的灵魂,允许我们在JVM运行时提供分析类,操作类的能力。 反射是一种在运行时检查和修改类、方法、属性等程序结构的能力。通过反射,可以动态地获取和操作程序的元数据,包括类的字段、方法、…

如何用 C++ 部署深度学习模型?

深度学习模型通常在诸如Python这样的高级语言中训练和验证,但在实际生产环境部署时,往往需要更高的执行效率和更低的资源占用。C作为一款性能卓越、低级别的编程语言,是部署深度学习模型的理想选择之一。本文将详细介绍如何在C环境下加载和运…

opengl日记11-opengl的transformtions变换示例

文章目录 环境代码CMakeLists.txt文件内容不变。vertexShaderSource.vsmain.cpp 总结参考 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.3.0 在<opengl学…

电子资金转账系统的分类、应用及其对银行业的影响

科技的飞速发展&#xff0c;计算机网络技术已广泛应用于各个领域&#xff0c;其中之一就是电子资金转账&#xff08;Electronic Funds Transfer&#xff0c;简称EFT&#xff09;系统。EFT系统作为金融业务电子化的重要实现手段&#xff0c;正逐步改变着传统银行业务的运作方式&…

Blender 3D建模要点

3d模型可以为场景的仿真模拟带来真实感&#xff0c;它还有助于更轻松地识别场景中的所有内容。 例如&#xff0c;如果场景中的所有对象都是简单的形状&#xff0c;如立方体和圆形&#xff0c;则很难在仿真中区分对象。 1、碰撞形状与视觉形状 像立方体和球体这样的简单形状&a…

开发指南015-前端缓存的信息

平台前端架构启动后&#xff0c;在store里存储了很多信息&#xff0c;可以通过getter取到&#xff1a; 1)访问token import { getToken } from /utils/qlm_auth getToken()可以获取该值 为空则没有登录 2) 用户信息 this.$store.getters.userId // 用户ID this.$sto…