设计模式-访问者(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

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

Redis 的 SUBSCRIBE命令详解

《Redis的SUBSCRIBE命令详解》Redis的SUBSCRIBE命令用于订阅一个或多个频道,以便接收发送到这些频道的消息,本文给大家介绍Redis的SUBSCRIBE命令,感兴趣的朋友跟随... 目录基本语法工作原理示例消息格式相关命令python 示例Redis 的 SUBSCRIBE 命令用于订

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

MySQL8 密码强度评估与配置详解

《MySQL8密码强度评估与配置详解》MySQL8默认启用密码强度插件,实施MEDIUM策略(长度8、含数字/字母/特殊字符),支持动态调整与配置文件设置,推荐使用STRONG策略并定期更新密码以提... 目录一、mysql 8 密码强度评估机制1.核心插件:validate_password2.密码策略级

从入门到精通详解Python虚拟环境完全指南

《从入门到精通详解Python虚拟环境完全指南》Python虚拟环境是一个独立的Python运行环境,它允许你为不同的项目创建隔离的Python环境,下面小编就来和大家详细介绍一下吧... 目录什么是python虚拟环境一、使用venv创建和管理虚拟环境1.1 创建虚拟环境1.2 激活虚拟环境1.3 验证虚