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

2024-03-22 07:36

本文主要是介绍设计模式-访问者(Visitor)模式详解和应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

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


前言

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


访问者模式介绍

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

结构

在这里插入图片描述

包含的角色

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

缺点:

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

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

总结

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

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

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

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

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

这篇关于设计模式-访问者(Visitor)模式详解和应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/834839

相关文章

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注