设计模式学习笔记 - 设计模式与范式 -行为型:13.访问者模式(下):为什么支持双分派的语言不需要访问者模式

概述

上篇文章,我们学习了访问者模式的原理和实现,并还原了访问者模式诞生的过程。总体来说,这个模式的代码实现比较难,所以应用场景不多。从应用开发的角度来说,它的确不是我们学习的重点。

本章,我们把访问者模式作为引子,一块讨论下这样两个问题:

  • 为什么支持双分派的语言不需要访问者模式?
  • 除了访问者模式,上篇文章的例子还有其他的实现方式吗?

为什么支持双分派的语言不需要访问者模式?

实际上,讲到访问者模式,大部分书籍或资料都会讲到 Double Dispatch,中文翻译为双分派。虽然学习访问者模式,并不用非得理解这个概念,但是为了让你在查看其他书籍或资料时,不会卡在这个概念上,本章在这里讲一下。

此外,个人觉得,学习 Double DIspatch 可以加深你对访问者模式的理解。

既然有 Double Dispatch,对应的就有 Signle Dispatch。

  • 所谓 Signle Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。
  • 所谓 Double Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。

如何理解 “Dispatch” 这个单词呢? 在面向对象编程语言中,可以把方法调用理解为一种消息传递,也就是 “Dispatch”。一个对象调用另一个对象的方法,就相当于给它发送一条消息。这条消息要包含对象名、方法名、方法参数。

如何理解 “Single” “Double” 这两个单词呢? “Single” “Double” 指的是执行那个对象的哪个方法,跟几个因素的运行时类型有关。

  • Signle Dispatch 之所以称为 “Single”,是因为执行哪个对象的哪个方法,只跟 “对象” 的运行时类型有关。
  • Double Dispatch 之所以称为 “Double”,是因为执行哪个对象的哪个方法,跟 “对象” 和 “方法参数” 两者的运行时类型有关。

具体到编程语言的语法机制,Signle Dispatch 和 Double Dispatch 跟多态和函数重载直接相关。当前主流的面向对象编程语言(比如,Java、C++、C#)都只支持 Signle Dispatch ,不支持 Double Dispatch。

接下来,拿 Java 语言来举例说明下。

Java 支持多态,代码可以在运行时获得对象的实际类型(也就是前面提到的运行时类型),然后根据实际类型决定调用哪个方法。尽管 Java 支持函数重载,但 Java 设计的函数重载,并不是在运行时,根据传递进函数的参数的实际类型,来决定调用重载函数。而是在编译时,根据传递进函数的参数的声明类型(也就是前面提到的编译时类型),来决定调用哪个重载函数。也就是说,具体执行哪个对象的哪个方法,只跟对象的运行时类型有关,跟函数参数的运行时类型无关。所以,Java 语言只支持 Signle Dispatch 。

这么说可能比较抽象,下面举个例子来说明下。

public class ParentClass {public void f() {System.out.println("I am ParentClass's f().");}
}public class ChildClass extends ParentClass {@Overridepublic void f() {System.out.println("I am ChildClass's f().");}
}public class SingleDispatchCLass {public void polymorphismFunction(ParentClass p) {p.f();}public void overloadsFunction(ParentClass p) {System.out.println("I am overloadFunction(ParentClass p)");}public void overloadsFunction(ChildClass c) {System.out.println("I am overloadFunction(ChildClass c)");}
}public class DemoMain {public static void main(String[] args) {SingleDispatchCLass demo = new SingleDispatchCLass();ParentClass p = new ChildClass();demo.polymorphismFunction(p); // 执行哪个对象的方法,由对象的实际类型决定demo.overloadsFunction(p); // 执行对象的哪个方法,由参数对象的声明类型决定}
}// 代码执行结果:
I am ChildClass's f().
I am overloadFunction(ParentClass p)

上面的代码中,demo.polymorphismFunction(p) 执行 p 的实际类型的 f() 函数,也就是 ChildClassf() 函数。 demo.overloadsFunction(p) 匹配的是重载函数中的 overloadsFunction(ParentClass p) ,也就是根据 p 的声明类型来决定匹配哪个重载函数。

假设 Java 语言支持 Double Dispatch,那下面的代码(摘抄至上篇文章)中 extractor.extract2txt(resourceFile) 的就不会报错。代码运行时,根据参数(resourceFile)的实际类型(PdfFilePptFileWordFile),来决定使用 extract2txt 的三个重载函数中的哪一个。下面的代码就能正常运行了,也就不需要访问者模式了。这也回达了为什么支持 Double Dispatch 的语言不需要访问者模式。

public abstract class ResourceFile {protected String filePath;public ResourceFile(String filePath) {this.filePath = filePath;}
}public class PptFile extends ResourceFile {public PptFile(String filePath) {super(filePath);}// ...
}public class PdfFile extends ResourceFile {public PptFile(String filePath) {super(filePath);}// ...
}public class WordFile extends ResourceFile {public PptFile(String filePath) {super(filePath);}// ...
}public class Extractor {public void extract2txt(PptFile pptFile) {// ...System.out.println("Extract PPT.");}public void extract2txt(PdfFile pdfFile) {// ...System.out.println("Extract PDF.");}public void extract2txt(WordFile wordFile) {// ...System.out.println("Extract Word.");}
}public class ToolApplication {public static void main(String[] args) {Extractor extractor = new Extractor();List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);for (ResourceFile resourceFile : resourceFiles) {extractor.extract2txt(resourceFile);}}private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {List<ResourceFile> resourceFiles = new ArrayList<>();// ... 根据后缀(ppt/pdf/word)由工厂方法创建不同类型的类对象(PptFile/PdfFile/WordFile)resourceFiles.add(new PdfFile("a.pdf"));resourceFiles.add(new PptFile("b.ppt"));resourceFiles.add(new WordFile("c.word"));return resourceFiles;}
}

除了访问者模式,上一节的例子还有其他的实现方案吗?

上篇文章,通过一个例子给你展示了,访问者模式是如何一步一步设计出来的。我们在回顾下那个例子。我们从网址站上爬取了很多资源文件,它们的格式有:PDF、PPT、Word。我们要开发一个工具来处理这批资源文件,这其实就包含抽取文本内容、压缩资源文件、提取文件信息等。

实际上,开发这个工具有很多种代码设计和实现思路。为了讲解访问者模式,上篇文章,我们使用了访问者模式来实现。实际上,还有其他的实现方法,比如,可以利用工程模式来实现,定义一个包含 extract2txt() 函数的 Extractor 接口。PdfExtractorPptExtractorWordExtractor 实现 Extractor 接口,并且在各自的 extract2txt() 函数中,分别实现 pdf、ppt、word 格式文件的文本内容抽取 。ExtractorFactory 工厂类根据不同的文件类型,返回不同的 Extractor

这个实现思路其实更加简单,代码如下所示。

public enum ResourceFileType {PDF,PPT,WORD;
}public abstract class ResourceFile {protected String filePath;public ResourceFile(String filePath) {this.filePath = filePath;}public abstract ResourceFileType getType();
}public class PdfFile extends ResourceFile {public PdfFile(String filePath) {super(filePath);}@Overridepublic ResourceFileType getType() {return ResourceFileType.PDF;}// ...
}public class PptFile extends ResourceFile {public PptFile(String filePath) {super(filePath);}@Overridepublic ResourceFileType getType() {return ResourceFileType.PPT;}// ...
}public class WordFile extends ResourceFile {public WordFile(String filePath) {super(filePath);}@Overridepublic ResourceFileType getType() {return ResourceFileType.WORD;}// ...
}public interface Extractor {void extract2txt(ResourceFile resourceFile);
}public class PdfExtractor implements Extractor {@Overridepublic void extract2txt(ResourceFile resourceFile) {// ...}
}public class PptExtractor implements Extractor {@Overridepublic void extract2txt(ResourceFile resourceFile) {// ...}
}public class WordExtractor implements Extractor {@Overridepublic void extract2txt(ResourceFile resourceFile) {// ...}
}public class ExtractorFactory {private static final Map<ResourceFileType, Extractor> extractors = new HashMap<>();static {extractors.put(ResourceFileType.PDF, new PdfExtractor());extractors.put(ResourceFileType.PPT, new PptExtractor());extractors.put(ResourceFileType.WORD, new WordExtractor());}public static Extractor getExtractor(ResourceFileType type) {return extractors.get(type);}
}public class ToolApplication {public static void main(String[] args) {List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);for (ResourceFile resourceFile : resourceFiles) {Extractor extractor = ExtractorFactory.getExtractor(resourceFile.getType());extractor.extract2txt(resourceFile);}}private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {List<ResourceFile> resourceFiles = new ArrayList<>();// ... 根据后缀(ppt/pdf/word)由工厂方法创建不同类型的类对象(PptFile/PdfFile/WordFile)resourceFiles.add(new PdfFile("a.pdf"));resourceFiles.add(new PptFile("b.ppt"));resourceFiles.add(new WordFile("c.word"));return resourceFiles;}
}

当需要添加新功能时,比如压缩文件,类似抽取文本内容功能的实现代码,只需要添加一个 Compressor 接口,PdfCompressorPptCompressorWordCompressor 三个实现类,以及创建它们的 CompressorFactory 工厂类即可。唯一需要修改的只有最上层的 ToolApplication。基本上符合 “对扩展开放、对修改关闭” 的设计原则。

  • 对于资源文件处理工具的例子,如果工具提供的功能并不是很多,只有几个而已,那更推荐使用工程模式的实现方式,比较代码清晰、易懂。
  • 相反,如果工具提供非常多的功能,比如有十几个,那更推荐使用访问者模式,因为访问者模式需要定义的类要比工程模式的少很多,类太多也会影响代码的可维护性。

总结

总体来说,访问者模式难以理解,应用场景有限,不是特别必需,不建议在项目中使用它。所以,对于上篇文章的处理资源文件的例子,更推荐使用工厂设计模式来设计和实现。

本章重点讲解了 Double Dispatch。在面向对象编程语言中,方法调用可以理解为一种消息传递(Dispatch)。一个对象调用另一个对象的方法,就相当于给它发送一条消息,这条消息起码要包含对象名、方法名和方法参数。

  • 所谓 Signle Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。
  • 所谓 Double Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。

具体到编程语言的语法机制,Signle Dispatch 和 Double Dispatch 跟多态和函数重载直接相关。当前主流的面向对象编程语言(如,Java、C++)都只支持 Signle Dispatch,不支持 Double Dispatch。

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

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

相关文章

高并发高性能接口中,异步打印并采集业务日志的实现方案

一、背景 高并发接口中&#xff0c;为了提高接口的高性能&#xff0c;在需要保存审计及操作记录的时候&#xff0c;往往有以下常见方案&#xff1a; 保存到redis数据库异步保存到mysql/mongodb/es等数据库logger打印业务日志&#xff0c;采集与展示则交由elk模块 对于第一种…

OpenLayers6实战,OpenLayers实现鼠标拖拽方式绘制平行四边形

专栏目录: OpenLayers实战进阶专栏目录 前言 本章介绍如何使用OpenLayers在地图上使用实现鼠标拖拽方式绘制平行四边形。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使用Yarn安装依赖yarn add olvue中如何使用: vue项目…

初学SSRF总结

什么是SSRF SSRF是由攻击者构造通过服务端发起请求的安全漏洞。通常情况下&#xff0c;SSRF的攻击对象是外部无法访问的内网&#xff08;因为是由服务端发起的请求所以攻击能够访问到内部系统&#xff09; 由于服务端提供了从其它服务器获取数据的功能&#xff0c;但是有没有…

SPLD论文笔记

SLPD论文笔记 题目&#xff1a;SLPD: Slide-Level Prototypical Distillation for WSIs 摘要 提高特征表示能力是许多全玻片病理图像 &#xff08;WSI&#xff09; 任务的基础。最近的工作在病理特异性自我监督学习&#xff08;SSL&#xff09;方面取得了巨大成功。然而&…

C++初阶:反向迭代器

reverse_iterator的封装实现 Reverse_Iterator.h namespace xx {// 所有容器的反向迭代器// 迭代器适配器template<class Iterator, class Ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iter…

Vue中如何使用Tailwind CSS样式?多次引用不成功?具体步骤怎么做?

一、安装Tailwind CSS和依赖 在你的Vue项目中安装Tailwind CSS及其依赖。你可以使用npm或yarn来安装。 npm install tailwindcsslatest postcsslatest autoprefixerlatest # 或者yarn add tailwindcsslatest postcsslatest autoprefixerlatest 二、初始化Tailwind CSS np…

Linux系统中MySQL数据库大小写敏感

问题描述 最近把网站向一台新的CentOS服务器中做迁移&#xff0c;把MySQL数据库和前后端站点全都部署完成后&#xff0c;网站启动之后一直在报表名不存在的错误。 开始略微疑惑&#xff0c;以为是做数据库备份的时候漏了表&#xff0c;检查后发现并不是这么回事。 略一思索&a…

Linux系统中安装 RPM 包

rpm -ivh 是在Linux系统中用来安装 RPM 包的命令。RPM&#xff08;Red Hat Package Manager&#xff09;是一种用于在基于Red Hat的Linux系统&#xff08;如Fedora、CentOS、Red Hat Enterprise Linux等&#xff09;上管理软件包的工具。 rpm -ivh 命令的作用&#xff1a; 安…

ETL工具-nifi干货系列 第九讲 处理器EvaluateJsonPath,根据JsonPath提取字段

1、其实这一节课本来按照计划一起学习RouteOnAttribute处理器&#xff08;相当于java中的ifelse&#xff0c;switch case 控制语句&#xff09;&#xff0c;但是在学习的过程中遇到了一些问题。RouteOnAttribute 需要依赖处理器EvaluateJsonPath&#xff0c;所以本节课我们一起…

一天300收入打底,​一个适合任何人的创业项目!

共享旅游卡项目&#xff0c;一天300收入打底&#xff0c;一个适合任何人的创业项目&#xff01; 只要你不懒&#xff0c;生活总过得不会太差。只要你不贪&#xff0c;就算不能大富大贵&#xff0c;至少不会负债累累。 人性最难戒掉的两个字&#xff1a;一个是懒&#xff0c;另…

编译原理 学习笔记

1、代码&#xff1a; (1 2) * 3 2、词法解析&#xff1a; 3、抽象语法树&#xff1a; 4、语法树递归下降求值&#xff1a; 先Current_Node是根节点乘号&#xff0c;乘号&#xff0c;是中缀运算符&#xff0c;找左子节点&#xff0c;是加号&#xff0c;加号是中缀表达式&…

220 基于matlab的考虑直齿轮热弹耦合的动力学分析

基于matlab的考虑直齿轮热弹耦合的动力学分析&#xff0c;输入主动轮、从动轮各类参数&#xff0c;考虑润滑油温度、润滑油粘度系数等参数&#xff0c;输出接触压力、接触点速度、摩擦系数、对流传热系数等结果。程序已调通&#xff0c;可直接运行。 220直齿轮热弹耦合 接触压力…

MATLAB有限元结构动力学分析与工程应用-徐斌|【PDF电子书+配套Matlab源码】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

【fiddler】弱网测试

目录 一、测试目的 二、步骤 2.1打开弱网模式 ​ 2.2设置网络参数 &#xff08;1&#xff09;打开Rules→Customize Rules&#xff1b; &#xff08;2&#xff09;找到下面框出的代码&#xff0c;在这里设置弱网参数值&#xff1b; &#xff08;3&#xff09;设置完成后&a…

软件行业之选:CRM系统如何赋能业务增长?

“CRM系统从整合营销渠道、自动化营销流程、强化客户全周期管理、增强服务能力、完善企业内部流程、开展数字化决策六个方面赋能软件行业。” 软件行业由于存在较高的技术壁垒&#xff0c;很多时候销售与客户沟通不顺畅&#xff0c;实施与客户沟通有难度&#xff0c;售后服务周…

gitignore:常用说明

示例&#xff1a; Java HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/**### IntelliJ IDEA.idea *.iws *.iml *.ipr### NetBeans/nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ logs/### VS Code.vscode/ 说明&#…

怎么给html文件本地启动一个服务去访问

首先得先安装node 要启动一个本地的 HTTP 服务器&#xff0c;请先安装 Node.js&#xff0c;然后通过命令行在 HTML 文件所在文件夹下运行 npx serve

Spring 面试题(四)

1. Spring 防止相同类型 Bean 注入异常的方法? 在Spring框架中&#xff0c;当存在多个相同类型的Bean时&#xff0c;如果尝试通过自动装配&#xff08;Autowiring&#xff09;来注入这些Bean&#xff0c;可能会引发异常&#xff0c;因为Spring不知道应该注入哪一个Bean实例。…

ubuntu maven 使用示例

ubuntu maven 使用示例 一、基本使用 1、安装 sudo apt update sudo apt install maven mvn -v2、创建项目 mvn archetype:generate -DgroupIdcom.example -DartifactIdmy-project -DarchetypeArtifactIdmaven-archetype-quickstart -DinteractiveModefalse # -DartifactId…

Java代码基础算法练习-统计学生成绩-2024.04.11

任务描述&#xff1a; 编写程序&#xff0c;输入n个(0<n<50)学生的成绩(输入-1结束)&#xff0c;要求统计并输出优秀(大任务描述:于85)、及格(60~84)和不及格(小于60)的学生人数。(成绩取值范围0~100) 任务要求&#xff1a; 代码示例&#xff1a; /*** 这个程序用于统计…