十七、访问者模式

文章目录

  • 1 基本介绍
  • 2 案例
    • 2.1 Element 接口
    • 2.2 Vehicle 抽象类
    • 2.3 Car 类
    • 2.4 Jeep 类
    • 2.5 VehicleCollection 类
    • 2.6 Action 抽象类
    • 2.7 Repair 类
    • 2.8 Drive 类
    • 2.9 Client 类
    • 2.10 Client 类的运行结果
    • 2.11 总结
  • 3 各角色之间的关系
    • 3.1 角色
      • 3.1.1 Element ( 元素 )
      • 3.1.2 ConcreteElement ( 具体元素 )
      • 3.1.3 ObjectStructure ( 对象结构 )
      • 3.1.4 Visitor ( 访问者 )
      • 3.1.5 ConcreteVisitor ( 具体访问者 )
      • 3.1.6 Client ( 客户端 )
    • 3.2 类图
  • 4 注意事项
  • 5 在源码中的使用
  • 6 双重派发
  • 7 优缺点
  • 8 适用场景
  • 9 总结


1 基本介绍

访问者模式(Visitor Pattern)是一种 行为型 设计模式,它 将 作用于某种数据结构中的各元素的操作 分离出来封装成独立的类,从而 在不改变数据结构的前提下添加作用于这些元素的新的操作为数据结构中的每个元素提供多种访问方式

本模式据说是最难的一个设计模式,请大家做好心理准备!

2 案例

本案例执行了对一系列车辆(其中含有轿车和吉普车)的各种行为(修理和驾驶),这里 一系列车辆 就相当于 数据结构各种行为 相当于 对数据结构中各元素的操作。虽然轿车和吉普车的实现差不多,但这里假设它们两个的实现有很大不同,这才需要将其放到两个类中。

2.1 Element 接口

public interface Element { // 接受访问的接口,实现后可以接受 Action 的子类的访问void accept(Action action); // 接受 action 的访问,执行具体的功能
}

2.2 Vehicle 抽象类

public abstract class Vehicle implements Element { // 车辆protected String vehicleName;// 获取车辆名称public String getVehicleName() {return vehicleName;}
}

2.3 Car 类

public class Car extends Vehicle { // 轿车public Car(String carName) {this.vehicleName = carName;}// 假设 轿车 还有一些别的功能与 吉普车 不同@Overridepublic void accept(Action action) {action.visit(this);}
}

2.4 Jeep 类

public class Jeep extends Vehicle { // 吉普车public Jeep(String jeepName) {this.vehicleName = jeepName;}// 假设 吉普车 还有一些别的功能与 轿车 不同@Overridepublic void accept(Action action) {action.visit(this);}
}

2.5 VehicleCollection 类

import java.util.ArrayList;
import java.util.List;public class VehicleCollection { // 车辆集合private List<Vehicle> vehicles = new ArrayList<>(); // 储存车辆的集合// 添加一个新的车辆到本集合中public void addVehicle(Vehicle vehicle) {vehicles.add(vehicle);}// 让集合中的所有车辆都进行一遍指定的 action 行为public void forEach(Action action) {for (Vehicle vehicle : vehicles) {vehicle.accept(action);}}
}

2.6 Action 抽象类

public abstract class Action { // 针对具体车辆的行为public abstract void visit(Car car); // 针对 轿车 的行为public abstract void visit(Jeep jeep); // 针对 吉普车 的行为
}

2.7 Repair 类

public class Repair extends Action { // 修理行为@Overridepublic void visit(Car car) {System.out.println("修理轿车[" + car.getVehicleName() + "],使用较小的位置");}@Overridepublic void visit(Jeep jeep) {System.out.println("修理吉普车[" + jeep.getVehicleName() + "],使用较大的位置");}
}

2.8 Drive 类

public class Drive extends Action { // 驾驶行为@Overridepublic void visit(Car car) {System.out.println("驾驶轿车[" + car.getVehicleName() + "],在城市的公路上行驶");}@Overridepublic void visit(Jeep jeep) {System.out.println("驾驶吉普车[" + jeep.getVehicleName() + "],在凹凸不平的路上行驶");}
}

2.9 Client 类

public class Client { // 客户端,测试了对一系列车辆的修理和驾驶行为public static void main(String[] args) {Action repair = new Repair(); // 修理行为Action drive = new Drive(); // 驾驶行为VehicleCollection vehicleCollection = new VehicleCollection();vehicleCollection.addVehicle(new Car("本田")); // 轿车vehicleCollection.addVehicle(new Jeep("牧马人")); // 吉普车// 进行修理行为vehicleCollection.forEach(repair);System.out.println("===============================");// 进行驾驶行为vehicleCollection.forEach(drive);}
}

2.10 Client 类的运行结果

修理轿车[本田],使用较小的位置
修理吉普车[牧马人],使用较大的位置
===============================
驾驶轿车[本田],在城市的公路上行驶
驾驶吉普车[牧马人],在凹凸不平的路上行驶

2.11 总结

本案例将 一系列车辆(其中含有轿车和吉普车)看作 数据结构,将 对单个车辆的行为(修理和驾驶)看作 对数据结构的访问,使用访问者模式将数据结构与对其的访问分隔开来,从而在不用修改原有代码的情况下,能够添加新的访问形式(添加新的对单个车辆的行为,例如购买),遵循了 开闭原则,提高了系统的灵活性和扩展性。

但是,如果想要添加一种新的数据结构(添加一种新的车辆,例如货车),则比较麻烦。需要在 Action 类中添加一个新的访问方法 public abstract void visit(? ?),这里的 ? 指的是添加的具体的数据结构的类型及其参数名。此外,还需要给现有的所有继承 Action 类的类都实现这个方法。

3 各角色之间的关系

3.1 角色

3.1.1 Element ( 元素 )

该角色是 Visitor 角色的访问对象声明了接受访问的 accept() 方法接收 Visitor 角色的参数。本案例中,Element 接口扮演了该角色。

3.1.2 ConcreteElement ( 具体元素 )

该角色负责 实现 Element 角色定义的接口。本案例中,Car, Jeep 类都在扮演该角色。

3.1.3 ObjectStructure ( 对象结构 )

该角色是 处理 Element 角色的集合有一个对集合中所有元素进行指定操作的方法。本案例中,VehicleCollection 类扮演了该角色。

3.1.4 Visitor ( 访问者 )

该角色负责 为 ObjectStructure 角色中的每个 ConcreteElement 角色定义 visit() 接口。本案例中,Action 抽象类扮演了该角色。

3.1.5 ConcreteVisitor ( 具体访问者 )

该角色负责 实现 Visitor 角色中定义的 接口具体处理每个 ConcreteElement 角色。本案例中,Repair, Drive 类都在扮演该角色。

3.1.6 Client ( 客户端 )

该角色负责 创建 ConcreteElement 角色和 ConcreteVisitor 角色使用 ObjectStructure 角色完成具体的业务逻辑。本案例中,Client 类扮演了该角色。

3.2 类图

alt text
说明:ConcreteVisitor 和 ConcreteElement 实际上是相互依赖的,为了避免关系过于复杂,图中没有表示。

4 注意事项

  • 设计复杂性:访问者模式需要定义多个角色(如访问者、元素、结构对象等)和接口,以及确保它们之间的正确协作,这会增加系统的复杂性和开发成本。当对象结构发生变化时,可能需要在多个访问者类中更新代码,这增加了维护的难度和成本。
  • 性能问题:访问者模式需要 遍历整个对象结构对每个元素执行操作,这可能会增加系统的响应时间或资源消耗。在处理 大型对象结构 时,这种性能问题可能更加明显。
  • 新元素类的添加:虽然访问者模式允许在不修改原有类结构的情况下增加 新的操作,但增加 新的元素类 时,需要在所有具体访问者类中增加对新元素类的操作实现。
  • 封装性破坏:访问者模式要求元素类暴露其内部状态给访问者,这可能会 破坏元素类的封装性。当元素类的内部状态比较复杂或敏感时,这种破坏可能会带来安全风险或数据一致性问题。
  • 单一职责原则:虽然访问者模式有助于遵守单一职责原则,但在实现时需要注意不要过度使用,以免增加系统的复杂性和维护成本。
  • 依赖倒转原则:尽量 让 访问者 依赖于 抽象类 而不是 具体类,以符合依赖倒转原则,降低系统间的耦合度。

5 在源码中的使用

java.nio.file 包中,使用 FileVisitor 接口时应用了访问者模式,其对应的角色如下:

  • ConcreteElement 角色Path 类可以被视为是访问者模式中的 ConcreteElement 角色,因为它是被访问的对象。注意,在 FileVisitor 使用的访问者模式中,没有直接定义接口或抽象类来表示 Element 角色。
  • ObjectStructure 角色文件系统本身 就是这个对象结构,Files.walkFileTree() 方法则是这个对象结构的遍历器,它接受一个起始目录和一个 FileVisitor 实例,然后遍历该目录及其子目录中的所有文件和目录。
  • Visitor 角色FileVisitor 接口,它包含一组方法,在遍历文件系统时会被调用:
    public interface FileVisitor<T> {// 在访问目录之前被调用FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)throws IOException;// 访问文件时被调用FileVisitResult visitFile(T file, BasicFileAttributes attrs)throws IOException;// 访问文件失败时被调用FileVisitResult visitFileFailed(T file, IOException exc)throws IOException;// 在访问目录之后被调用FileVisitResult postVisitDirectory(T dir, IOException exc)throws IOException;
    }
    
  • ConcreteVisitor 角色:通过实现 FileVisitor 接口来创建自己的具体访问者,定义在访问文件或目录时应该执行的具体逻辑。例如,以下是一个简单的 FileVisitor 实现,它遍历一个目录树,并打印出所有文件的名称:
    import java.io.IOException;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;public class Test {public static void main(String[] args) throws IOException {Path start = Paths.get("/dir"); // 指定具体的目录Files.walkFileTree(start, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs)throws IOException {System.out.println(file);return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)throws IOException {return FileVisitResult.CONTINUE;}// 可以根据需要重写其他方法});}
    }
    

使用访问者模式,就可以通过实现 FileVisitor 接口来对文件系统(相当于一个数据结构)进行特定的操作,这样遵循了 开闭原则,使得 FileVisitor 接口和 Files.walkFileTree() 方法能够重复使用,即具有 复用性

6 双重派发

访问者模式中的 双重派发 是该模式的一个核心特性,它指的是 当 一个具体访问者对象 访问 一个具体元素对象 时,会根据这两个对象的类型(即 具体访问者类型 和 具体元素类型)来动态地选择并执行相应的方法。这种机制使得 可以在不修改元素类代码的前提下,为元素类添加新的操作

实现方式

  1. 元素类中的 accept() 方法:ConcreteElement 中通常包含一个 accept() 方法,该方法接受一个访问者对象作为参数。当 accept() 方法被调用时,它会 将自身作为参数 传递给访问者对象的某个方法
  2. 访问者类中的操作方法:ConcreteVisitor 中定义了多个操作方法,每个方法对应于一种具体元素。这些方法的具体实现会 根据 具体元素对象的类型 执行相应的操作
  3. 动态方法调用:当元素对象的 accept() 方法被调用时,它会根据具体访问者对象的类型和自身的类型(也就是 ConcreteVisitor 角色 和 ConcreteElement 角色的具体类型),在运行时动态地选择并执行访问者对象中的相应方法。这种 动态方法调用 的过程就是双重派发的实现。

7 优缺点

优点

  • 扩展性好:访问者模式使得 增加新的操作变得容易。当需要给对象结构中的元素添加新的操作时,只需增加一个新的访问者类即可,而无需修改原有的类结构,这符合开闭原则(对扩展开放,对修改关闭)。
  • 灵活性强:访问者模式将 数据结构作用于结构上的操作 解耦,使得 操作 可以相对自由地演化,而不影响 数据结构,这提高了系统的 灵活性
  • 复用性好:访问者模式可以 通过访问者来定义整个对象结构通用的功能,提高了代码的 复用性。特别是当多个访问者共享某些操作时,可以将这些操作提取到访问者接口或父类中,避免代码重复。
  • 符合单一职责原则:访问者模式将相关的操作封装在一起,形成一个访问者类,使得 每个访问者类的职责都比较单一,有助于降低类的复杂度。

缺点

  • 实现复杂:访问者模式的实现 相对复杂,需要定义多个角色和接口,并且需要确保它们之间的正确协作,这无疑会增加系统的复杂性和开发成本。同时,由于访问者模式涉及多个类的交互,因此也增加了系统出错的概率。
  • 难以增加新的具体元素:当需要为对象结构增加新的具体元素时,需要在所有具体访问者类中增加对这个新元素类的操作实现,增加了维护成本。
  • 违反依赖倒置原则:访问者模式在某种程度上违反了依赖倒置原则,因为 具体访问者类 依赖于 具体元素类,而不是依赖于抽象。这可能导致系统耦合度增加,降低系统的可测试性和可维护性。
  • 破坏封装:访问者模式要求 具体元素类 暴露其内部状态给 具体访问者,这可能会破坏元素类的封装性。当元素类的内部状态比较复杂或敏感时,这种破坏可能会带来 安全风险数据一致性 问题。
  • 性能问题:在某些情况下,访问者模式可能会导致性能问题。因为 访问者需要遍历整个对象结构对每个元素执行操作,这可能会增加系统的响应时间或资源消耗。在处理 大型对象结构 时,这种性能问题可能更加明显。

8 适用场景

  • 对象结构复杂且稳定,但操作频繁变化:当系统中的 对象结构 相对复杂且稳定,但经常需要对其中的元素进行多种不同的 操作 时,可以使用访问者模式。这样可以在不修改对象结构的前提下,通过增加新的访问者类来扩展操作。
  • 需要收集操作结果:如果需要对 对象结构 中的元素执行 一系列操作,并需要 收集这些操作的结果进行后续处理 时,可以使用访问者模式。访问者可以在遍历对象结构的过程中,逐步收集操作结果,并在遍历结束后进行统一处理。
  • 设计模式混合使用:在一些复杂的系统中,可能需要将访问者模式与其他设计模式(如组合模式、迭代器模式等)混合使用,以实现更复杂的功能。例如,可以使用 组合模式构建对象结构,然后使用 访问者模式遍历这个结构 并 执行操作
  • 跨平台或跨语言操作:在某些情况下,系统可能需要 与不同的平台或语言进行交互,并 对这些平台或语言中的对象执行操作。使用访问者模式可以将这些操作封装在访问者类中,并通过访问者接口来统一调用,从而简化跨平台或跨语言的操作过程。

9 总结

访问者模式 是一种 行为型 设计模式,它 分离了 数据结构 和 对数据结构的操作,使得能够很容易地添加一种新的操作,遵守了 开闭原则,增强了系统的灵活性和扩展性。但是,这种模式实现起来比较复杂,容易犯错,还难以在数据结构中增加新的具体元素类型,所以在使用前需要慎重考虑。

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

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

相关文章

靓图!多点创新!CEEMDAN-Kmeans-VMD-CNN-LSTM-Attention双重分解+卷积长短期+注意力多元时间序列预测

靓图&#xff01;多点创新&#xff01;CEEMDAN-Kmeans-VMD-CNN-LSTM-Attention双重分解卷积长短期注意力多元时间序列预测 目录 靓图&#xff01;多点创新&#xff01;CEEMDAN-Kmeans-VMD-CNN-LSTM-Attention双重分解卷积长短期注意力多元时间序列预测效果一览基本介绍程序设计…

LVS 调度器 nat和DR模式

lvs-nat 修改请求报文的目标IP,多目标IP的DNAT 配置网络 LVS主机 注意网卡的顺序 &#xff08;nat和主机模式&#xff09; [rootlvs ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection [connection] idens160 typeethernet interface-nameens160 ​ [ip…

Linux使用学习笔记3 系统运维监控基础

系统运维监控类命令 查询每个进程的线程数 for pid in $(ps -ef | grep -v grep|grep "systemd" |awk {print $2});do echo ${pid} > /tmp/a.txt;cat /proc/${pid}/status|grep Threads > /tmp/b.txt;paste /tmp/a.txt /tmp/b.txt;done|sort -k3 -rn for pid…

数据结构与算法-16高级数据结构_图论(图论基础)

图论基础 1 什么是图 1.1 基础定义 图&#xff08;Graph&#xff09;是一个用于描述一组对象之间关系的数学结构。这些对象被称为顶点&#xff08;Vertex&#xff09;&#xff0c;也称为节点&#xff08;Node&#xff09;或点&#xff08;Point&#xff09;&#xff0c;而对…

2024国赛Word论文模板【一键生成式操作】

一、比赛介绍 该竞赛创办于1992年&#xff0c;每年一届&#xff0c;是首批列入“高校学科竞赛排行榜”的19项竞赛之一。2023年&#xff0c;来自全国及美国、澳大利亚、马来西亚的1685所院校/校区、59611队(本科54158队、专科5453队)、近18万人报名参赛。 而今年的国赛马上就要…

【CTF | WEB】001、攻防世界WEB题目之backup

文章目录 backup题目描述:解题思路&#xff1a;解题过程&#xff1a; backup 题目描述: X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; 进入题目后显示&#xff1a; 解题思路&#xff1a; 在进行网站安全检查时&#xf…

网络协议四 物理层,数据链路层

从这一节开始学习 五层模型。学习方法是从最底层物理层开始学习 七层模型 五层模型 各个层用的协议&#xff0c;以及加上协议后的称谓 各个层的作用 应用层&#xff1a;可以认为是原始数据&#xff0c;该数据称为 报文&#xff0c;用户数据。 运输层&#xff1a;也叫传输层&am…

全网超详细攻略——LVS原理详解及部署

目录 一、LVS原理 1.LVS简介 2.LVS结构 3.IP负载均衡技术 4.LVS相关术语 二、LVS负载均衡四种工作模式 1.LVS-DR模式 2.LVS-NAT模式 3.LVS-TUN模式&#xff08;了解&#xff09; 4.FULL-NAT模式&#xff08;了解&#xff09; 三、LVS负载均衡十种调度算法 四、LVS部…

米思奇安装——Mac版本

米思奇安装——Mac版本 1.下载 访问米思奇官网https://mixly.org/bnu-maker/mixl2.0rc 打开官网后在首页点击导航栏的软件平台&#xff0c;选择Mixly离线版 点击Mixly2.0RC4发布下载。 进入百度网盘分享的文件&#xff0c;选择Mac一键更新版本&#xff0c;等待下载完成。 …

尚品汇-ES(三十一)

目录&#xff1a; &#xff08;1&#xff09;封装搜索相关实体对象 &#xff08;2&#xff09;搜索接口封装 &#xff08;3&#xff09;在service-list-client模块添加远程接口 &#xff08;1&#xff09;封装搜索相关实体对象 搜索参数实体&#xff1a;SearchParam 搜索参…

第七节 流编辑器sed(stream editor)(7.1)

一,sed简介 sed是一种流编辑器,处理时,把当前处理的行存储在临时缓冲区中,称为模式空间,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕,接着处理下一行,这样不断重复,直到文件末尾,文件内容并没有改变 二,sed的语法 2,1,基本语法 sed options ... […

AI学习记录 - gpt如何进行token化,理论知识,以GPT2为举例

AI学习记录已经发了十几篇&#xff0c;大佬们可以看看&#xff0c;如果有帮助动动小手点赞 token入门版&#xff0c;有空会更新具体代码操作 GPT4当中&#xff0c;我们提问问题是按照token进行扣费的&#xff0c;那到底什么是token&#xff1f; 在不同的语言模型当中&#x…

gradio之进度条

输出控件显示进度&#xff0c;进度结束显示控件结果 import gradio as gr import timedef slowly_reverse(word, progressgr.Progress()):progress(0, desc"Starting")time.sleep(1)progress(0.05)new_string ""for letter in progress.tqdm(word, desc&…

C++ 特性之vector详解 + 联合opencv使用

C 特性之vector详解 联合opencv使用 在C中&#xff0c;遍历vector并删除元素需要小心处理迭代器失效的问题。通常推荐的方法是使用迭代器进行遍历&#xff0c;并在需要删除元素时使用erase函数。这里给出一个示例代码&#xff0c;演示如何安全地遍历vector并删除特定条件的元…

计算机毕业设计 家电销售展示平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

智能识别,2024年SD卡数据恢复软件的智能进化

除了手机之外现在有不少的设备还是依靠SD卡来存储数据&#xff0c;比如相机、摄像头、无人机等。有的时候会因为一些意外的情况导致数据丢失&#xff0c;那是真的丢失了吗&#xff1f;大部分情况还是可以依靠sd卡数据恢复工具来找回这些“消失”的数据哦。 1.福昕数据恢复 链…

python正则表达式以及re模块的运用完成文本处理(搜索、匹配、替换等文本操作)

1.正则表达式 正则表达式是一种强大的文本处理工具&#xff0c;用于搜索、匹配、替换等文本操作。 2.通过re模块实现正则表达式的操作 Python中的re模块是Python的标准库之一&#xff0c;它提供了对正则表达式的支持。正则表达式是一种强大的文本处理工具&#xff0c;用于搜…

AI赋能招聘:效率与公平的双重提升

一、引言&#xff1a;AI赋能招聘新纪元 在21世纪的数字化浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术以前所未有的速度渗透到社会经济的各个领域&#xff0c;深刻地改变着我们的生活方式与工作模式。人力资源管理&#xff0c;作为企业战略的重要组成部分&#…

[C++][opencv]基于opencv实现photoshop算法色相和饱和度调整

【测试环境】 vs2019 opencv4.8.0 【效果演示】 【核心实现代码】 HSL.hpp #ifndef OPENCV2_PS_HSL_HPP_ #define OPENCV2_PS_HSL_HPP_#include "opencv2/core.hpp" using namespace cv;namespace cv {enum HSL_COLOR {HSL_ALL,HSL_RED,HSL_YELLOW,HSL_GREEN,HS…

在IIS上部署ASP.NET Core Web API和Blazor Wasm详细教程

前言 前段时间我们完成了七天.NET 8 操作 SQLite 入门到实战的开发系列教程&#xff0c;有不少同学留言问如何将项目发布部署到IIS上面运行。本篇文章我们就一起来讲讲在IIS上部署ASP.NET Core Web API和Blazor Wasm。 前提条件 安装.NET Core SDK https://dotnet.microsoft…