【设计模式】通过访问者模式实现分离算法与对象结构

概述

定义:封装一些作用于某种数据结构中的各元素的操作(将数据结构于元素进行分离),它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

结构

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。

  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。

  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。

  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

案例实现

【例】给宠物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人

  • 具体访问者角色:主人、其他人

  • 抽象元素角色:动物抽象类

  • 具体元素角色:宠物狗、宠物猫

  • 结构对象角色:主人家

类图如下:

代码如下:

创建抽象访问者接口

public interface Person {void feed(Cat cat);void feed(Dog dog);
}

创建不同的具体访问者角色(主人和其他人),都需要实现 Person接口

public class Owner implements Person {@Overridepublic void feed(Cat cat) {System.out.println("主人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("主人喂食狗");}
}public class Someone implements Person {@Overridepublic void feed(Cat cat) {System.out.println("其他人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("其他人喂食狗");}
}

定义抽象节点 – 宠物

public interface Animal {void accept(Person person);
}

定义实现Animal接口的 具体节点(元素)

public class Dog implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("好好吃,汪汪汪!!!");}
}public class Cat implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("好好吃,喵喵喵!!!");}
}

定义对象结构,此案例中就是主人的家

public class Home {private List<Animal> nodeList = new ArrayList<Animal>();public void action(Person person) {for (Animal node : nodeList) {node.accept(person);}}//添加操作public void add(Animal animal) {nodeList.add(animal);}
}

测试类

public class Client {public static void main(String[] args) {Home home = new Home();home.add(new Dog());home.add(new Cat());Owner owner = new Owner();home.action(owner);Someone someone = new Someone();home.action(someone);}
}

优缺点

优点:

  • 扩展性好:在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为:通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

缺点:

  • 对象结构变化很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

扩展

访问者模式用到了一种双分派的技术。

**1,分派:**变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

2,动态分派:

通过方法的重写支持动态分派。

public class Animal {public void execute() {System.out.println("Animal");}
}public class Dog extends Animal {@Overridepublic void execute() {System.out.println("dog");}
}public class Cat extends Animal {@Overridepublic void execute() {System.out.println("cat");}
}public class Client {public static void main(String[] args) {Animal a = new Dog();a.execute();Animal a1 = new Cat();a1.execute();}
}

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

3,静态分派:

通过方法重载支持静态分派。

public class Animal {
}public class Dog extends Animal {
}public class Cat extends Animal {
}public class Execute {public void execute(Animal a) {System.out.println("Animal");}public void execute(Dog d) {System.out.println("dog");}public void execute(Cat c) {System.out.println("cat");}
}public class Client {public static void main(String[] args) {Animal a = new Animal();Animal a1 = new Dog();Animal a2 = new Cat();Execute exe = new Execute();exe.execute(a);exe.execute(a1);exe.execute(a2);}
}

运行结果:

这个结果可能出乎一些人的意料了,为什么呢?

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

4,双分派:

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。

 class Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Dog extends Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Cat extends Animal {public void accept(Execute exe) {exe.execute(this);}
}public class Execute {public void execute(Animal a) {System.out.println("animal");}public void execute(Dog d) {System.out.println("dog");}public void execute(Cat c) {System.out.println("cat");}
}public class Client {public static void main(String[] args) {Animal a = new Animal();Animal d = new Dog();Animal c = new Cat();Execute exe = new Execute();a.accept(exe);d.accept(exe);c.accept(exe);}
}

在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。

说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。

运行结果如下:

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

往期推荐

  • 《SpringBoot》EasyExcel实现百万数据的导入导出
  • 《SpringBoot》史上最全SpringBoot相关注解介绍
  • Spring框架IoC核心详解
  • 万字长文带你窥探Spring中所有的扩展点
  • 如何实现一个通用的接口限流、防重、防抖机制
  • 万字长文带你深入Redis底层数据结构
  • volatile关键字最全原理剖析

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

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

相关文章

低版本 Linux 系统通过二进制方式升级部署高版本 Docker

​ 一、背景&#xff1a; 在一些 Linux 系统中&#xff0c;由于系统自带的软件源版本较低&#xff0c;或者因网络、权限等限制无法直接通过源文件来升级到最新版本的 Docker。这种情况下&#xff0c;采用二进制方式升级部署高版本 Docker 就成为一种有效的解决方案。下面将详…

SpringAI+Ollama+DeepSeek本地大模型调用

前言 大型语言模型&#xff08;LLM&#xff09;在自然语言处理领域取得了突破性进展&#xff0c;但其庞大的计算资源需求和高昂的调用成本&#xff0c;使得许多开发者望而却步。如何高效、便捷地调用大模型&#xff0c;并将其应用于实际场景&#xff0c;成为了亟待解决的问题。…

【大模型科普】AIGC技术发展与应用实践(一文读懂AIGC)

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能&#xff08;AI&#xff09;通过算法模拟人类智能&#xff0c;利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络&#xff08;如ChatGPT&…

数据结构与算法:归并排序

目录 归并排序的基本思想 归并排序的特性总结 代码 归并排序的非递归版 归并排序的基本思想 归并排序是建立在归并操作上的一种有效的排序算法。改算法是采用分治法的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列…

阿里云操作系统控制台评测:国产AI+运维 一站式运维管理平台

阿里云操作系统控制台评测&#xff1a;国产AI运维 一站式运维管理平台 引言 随着云计算技术的飞速发展&#xff0c;企业在云端的运维管理面临更高的要求。阿里云操作系统控制台作为一款集运维管理、智能助手和系统诊断等多功能于一体的工具&#xff0c;正逐步成为企业高效管理…

爬虫案例十三js逆向模拟登录中大网校

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、网站分析二、代码 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; js 逆向模拟登录中大网校 提示&#xff1a;以下是本篇文章正文内…

sql靶场--布尔盲注(第八关)保姆级教程

目录 布尔盲注&#xff08;第八关&#xff09; 1.判断 2.确认布尔盲注 3.手工尝试布尔盲注 表名字符 表数 表名长度 表字符 字段数 字段名长度 字段字符 4.脚本布尔盲注注入 布尔盲注&#xff08;第八关&#xff09; 1.判断 布尔盲注了&#xff0c;这种页面只会有…

【C++入门】变量和基本类型

目录 一、 基本内置类型 1.1. 整型&#xff08;Integer Types&#xff09; 1.2. 浮点型&#xff08;Floating-point Types&#xff09; 1.3. 字符型&#xff08;Character Type&#xff09; 1.4. 布尔型&#xff08;Boolean Type&#xff09; 1.5. 示例代码 二、变量声明…

JVM内存结构笔记03-方法区

文章目录 方法区1.定义2.组成方法区与永久代和元空间的关系为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢? 3.方法区常用参数4.运行时常量池常量池运行时常量池定义查看class文件 方法区 1.定义 方法区属于是 JVM 运行时数据区域的一块逻辑区域&#xff0c;是各个…

数据库语句

环境变量path下的目录是系统目录。 #include <iostream> #include <mysql.h> #pragma comment(lib,"libmysql.lib")//链接libmysql.dll动态库的中间桥 // MYSQL* conn;//数据库句柄。后面还有网络句柄&#xff08;用来网络收发数据&#xff09; bool co…

Word 小黑第15套

对应大猫16 修改样式集 导航 -查找 第一章标题不显示 再选中文字 点击标题一 修改标题格式 格式 -段落 -换行和分页 勾选与下段同页 添加脚注 &#xff08;脚注默认位于底部 &#xff09;在脚注插入文档属性&#xff1a; -插入 -文档部件 -域 类别选择文档信息&#xff0c;域…

【从零开始学习计算机科学】编译原理(七)运行时刻环境

【从零开始学习计算机科学】编译原理(七)运行时刻环境 运行时刻环境存储组织空间的栈式分配活动树活动记录和控制栈简单栈式存贮分配C语言的过程调用和过程返回时的存贮管理堆式存储分配堆式存储分配的功能垃圾回收基于跟踪的垃圾回收短停顿垃圾回收运行时刻环境 存储组织 …

一维下料之 *贪心算法* —— CAD c#二次开发

一维下料之贪心算法&#xff0c;需求如下 已知条件 我们有一批长度为 380 米 的原材料&#xff08;例如钢管、木材等&#xff09;。 切割需求 需要从这些原材料中切割出以下长度的小段&#xff1a;42 米&#xff1a;需要 13 段 140米&#xff1a;需要 23 段 130 米&#xff1a…

刷leetcode hot100--动态规划3.12

第一题乘积max子数组[1h] emmmm感觉看不懂题解 线性dp【计划学一下acwing&#xff0c;挨个做一下】 线性动态规划 相似题解析 最长上升子序列 最大上升子序列和 最大连续子段和 乘积最大子数组_哔哩哔哩_bilibili 比较奇怪的就是有正负数和0&#xff0c;如何处理&#xff1f…

Linux安装升级docker

Linux 安装升级docker Linux 安装升级docker背景升级停止docker服务备份原docker数据目录移除旧版本docker安装docker ce恢复数据目录启动docker参考 安装找到docker官网找到docker文档删除旧版本docker配置docker yum源参考官网继续安装docker设置开机自启配置加速测试 Linux …

pycharm + anaconda + yolo11(ultralytics) 的视频流实时检测,保存推流简单实现

目录 背景pycharm安装配置代码实现创建本地视频配置 和 推流配置视频帧的处理和检测框绘制主要流程遇到的一些问题 背景 首先这个基于完整安装配置了anaconda和yolo11的环境&#xff0c;如果需要配置开始的话&#xff0c;先看下专栏里另一个文章。 这次的目的是实现拉取视频流…

LLM:了解大语言模型

大型语言模型&#xff08;Large language models&#xff0c;LLMs&#xff09;&#xff0c;如 OpenAI 的 ChatGPT &#xff0c;或者 DeepSeek 等&#xff0c;是过去几年中开发出来的深度神经网络模型。它们为自然语言处理&#xff08;natural language processing&#xff0c;N…

Linux多进程学习

一、什么是多进程 1.多任务程序能够同时做多件事情&#xff0c;如QQ同时聊天和上传下载。 2.多任务程序在应用开发中非常普遍&#xff0c;是必须掌握的基本概念。 二、进程的创建与资源分配 1.操作系统在创建进程时会分配内存资源、CPU资源和时间片。 2.进程的内容包括代码、…

「Unity3D」UGUI将元素固定在,距离屏幕边缘的某个比例,以及保持元素自身比例

在不同分辨率的屏幕下&#xff0c;UI元素按照自身像素大小&#xff0c;会发生位置与比例的变化&#xff0c;本文仅利用锚点&#xff08;Anchors&#xff09;使用&#xff0c;来实现UI元素&#xff0c;固定在某个比例距离的屏幕边缘。 首先&#xff0c;将元素的锚点设置为中心&…

STM32 内置的通讯协议

数据是以帧为单位发的 USART和UART的区别就是有没有同步功能 同步是两端设备有时钟连接&#xff0c;异步是没时钟连接&#xff0c;靠约定号的频率&#xff08;波特率&#xff09;接收发送数据 RTS和CTS是用来给外界发送已“可接收”或“可发送”信号的&#xff0c;一般用不到…