【设计模式--行为型--访问者模式】
设计模式--行为型--访问者模式
- 访问者模式
- 定义
- 结构
- 案例
- 优缺点
- 使用场景
- 扩展
- 分派
- 动态分派
- 静态分派
- 双分派
访问者模式
定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。
结构
- 抽象访问者角色(Visitor):定义了对每一个元素访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来说于元素类个数是一样的,从这点上来看,访问者模式要求元素类的个数不能改变。
- 具体访问者角色(Concrete Visitor):给出对每一个元素类访问时所产生的具体行为。
- 抽象元素角色(Element):定义了一个接受访问者的方法,其意义是指,每一个元素都要可以被访问者访问。
- 具体元素角色(Concrete Element):提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构角色(Object Structure):定义当中所提到的对象结构,对象机构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问。
案例
给宠物喂食
- 访问者角色:给宠物喂食的人
- 具体访问者角色:主人,其他人
- 抽象元素角色:动物抽象类
- 具体元素角色:宠物狗,宠物猫
- 结构对象角色:主人家
类图:

/*** 抽象元素角色类*/
public interface Animal {// 接受访问者访问的功能void accept(Person person);
}
/*** 具体元素角色类 猫*/
public class Cat implements Animal{@Overridepublic void accept(Person person) {person.feed(this);System.out.println("喵喵喵~");}
}
/*** 具体元素角色类 狗*/
public class Dog implements Animal{@Overridepublic void accept(Person person) {person.feed(this);System.out.println("汪汪汪~");}
}
/*** 抽象访问者角色类*/
public interface Person {void feed(Cat cat);void feed(Dog dog);
}
/*** 具体访问者角色类 自己*/
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 class Home {// 声明一个集合对象,用来存储元素对象private List<Animal> nodeList = new ArrayList<>();// 添加元素public void add(Animal animal){nodeList.add(animal);}public void action(Person person){// 遍历集合获取每一个元素,让访问者访问每一个元素for (Animal animal : nodeList) {animal.accept(person);}}
}
public class Test01 {public static void main(String[] args) {// 创建home对象Home home = new Home();// 添加元素home.add(new Cat());home.add(new Dog());// 创建主人对象Owner owner = new Owner();// 让主人喂所有的宠物home.action(owner);}
}

优缺点
- 优点
- 扩展性好,在不修改对象结构中元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好,通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
- 分离无关行为,通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
- 缺点
- 对象结构变化困难,在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,违背了开闭原则
- 违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类。
使用场景
- 对象结构相对稳定,但操作算法经常变化
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
扩展
访问者用到了一种双分派技术。
分派
变量被声明时的类型叫做变量的静态类型,又称明显类型;而变量所引起的对象的真实类型又叫做变量的实际类型。比如Map map = new HashMap(),map变量的静态类型是Map,实际类型是HashMap。根据对象的类型而对方法进行的选择。就是分派(Dispatch),分派又分两种,静态分派和动态分派。
- 静态分派,发生在编译时期,分派根据静态类型信息发生。方法重载就是静态分派。
- 动态分派,发生在运行时期,动态分派动态的置换掉某个方法,Java就是通过方法的重写支持动态分派。
动态分派
通过方法的重写支持动态分派
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 Test{public static void main(String[] args) {Animal animal = new Dog();animal.execute();Animal animal1 = new Cat();animal1.execute();}
}
上面是多态,运行执行的是子类中的方法。
Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真是类型;而方法的调用则是根据对象的真实类型,而不是静态类型。
静态分派
通过方法重载支持静态分派
public class Animal {public void execute(){System.out.println("animal");}
}public class Dog extends Animal{
}public class Cat extends Animal{
}public class Execute{public void execute(Animal animal){System.out.println("animal");}public void execute(Cat cat){System.out.println("cat");}public void execute(Dog dog){System.out.println("dog");}
}public class Test{public static void main(String[] args) {Animal animal = new Animal();Animal animal2 = new Cat();Animal animal3 = new Dog();Execute execute = new Execute();execute.execute(animal); // animalexecute.execute(animal2); // animalexecute.execute(animal3); // animal}
}
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
双分派
在选择一个方法的时候,不仅仅要根据消息接收者的运行时区别,还要根据参数的运行时区别
public class Animal {public void accept(Execute execute){execute.execute(this);}
}public class Dog extends Animal{public void accept(Execute execute) {execute.execute(this);}
}public class Cat extends Animal{public void accept(Execute execute) {execute.execute(this);}
}public class Execute{public void execute(Animal animal){System.out.println("animal");}public void execute(Cat cat){System.out.println("cat");}public void execute(Dog dog){System.out.println("dog");}
}public class Test{public static void main(String[] args) {Animal animal = new Animal();Animal animal2 = new Cat();Animal animal3 = new Dog();Execute execute = new Execute();animal.accept(execute); // animalanimal2.accept(execute); // catanimal3.accept(execute); // dog}
}
上面代码中,客户端将Execute对象作为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也是将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
相关文章:
【设计模式--行为型--访问者模式】
设计模式--行为型--访问者模式 访问者模式定义结构案例优缺点使用场景扩展分派动态分派静态分派双分派 访问者模式 定义 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。 结构 抽象访问者角色&…...
[最后一个月征稿、ACM独立出版】第三届密码学、网络安全和通信技术国际会议(CNSCT 2024)
第三届密码学、网络安全和通信技术国际会议(CNSCT 2024) 2024 3rd International Conference on Cryptography, Network Security and Communication Technology 一、大会简介 随着互联网和网络应用的不断发展,网络安全在计算机科学中的地…...
android —— PopupWindow
一、常用方法: 1、设置显示的位置 // 一个参数 popupWindow.showAsDropDown(v); //参数1: popupWindow关联的view // 参数2和3:相对于关联控件的偏移量popupWindow.showAsDropDown(View anchor, int xoff, int yoff)2、是否会获取焦点 popupWindow.se…...
mysql部署 --(docker)
先查找MySQL 镜像 Docker search mysql ; 拉取mysql镜像,默认拉取最新的; 创建mysql容器,-p 代表端口映射,格式为 宿主机端口:容器运行端口 -e 代表添加环境变量,MYSQL_ROOT_PASSWORD是root用户…...
基于多智能体系统一致性算法的电力系统分布式经济调度策略MATLAB程序
微❤关注“电气仔推送”获得资料(专享优惠) 参考文献: 主要内容: 应用多智能体系统中的一致性算法,以发电机组的增量成本和柔性负荷的增量效益作为一致性变量,设计一种用于电力系统经济调度的算法&#x…...
Android : SensorManager 传感器入门 简单应用
功能介绍:转动手机 图片跟着旋转 界面: activity_main.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/andr…...
《点云处理》 点云去噪
前言 通常从传感器(3D相机、雷达)中获取到的点云存在噪点(杂点、离群点、孤岛点等各种叫法)。噪点产生的原因有不同,可能是扫描到了不想要扫描的物体,可能是待测工件表面反光形成的,也可能是相…...
npm login报错:Public registration is not allowed
npm login报错:Public registration is not allowed 1.出现场景2.解决 1.出现场景 npm login登录时,出现 2.解决 将自己的npm镜像源改为npm的https://registry.npmjs.org/这个,解决!...
OpenHarmony 启动流程优化
目前rk3568的开机时间有21s,统计的是关机后从按下 power 按键到显示锁屏的时间,当对openharmony的系统进行了裁剪子系统,系统app,禁用部分服务后发现开机时间仅仅提高到了20.94s 优化微乎其微。在对init进程的log进行分析并解决其…...
解决腾讯云CentOS 6硬盘空间不足问题:从快照到数据迁移
引言: 随着数据的不断增加,服务器硬盘空间不足变成了许多运维人员必须面对的问题。此主机运行了httpd(apache服务),提供对外web访问服务,web资源挂载在**/data/wwwroot目录下,http日志存放在/data/wwwlogs目录下&…...
org.slf4j日志组件实现日志功能
slf4j 全称是Simple Logging Facade for Java。facade是一种设计模式。 slf4j 是一个抽象程度更高的日志组件,本身并不提供实际的日志功能。实际的日志功能是通过log4j等日志组件实现,而使用者只需要关心 slf4j 给出的API。 slf4j 仅仅是一个为Java程序提…...
3D小球跑酷
目录 一、前言 二、开发环境 三、场景搭建 1. 创建项目 2. 创建场景内物体 2.1 创建跑道 2.2 创建玩家 2.3 创建障碍物 2.4 改变跑道和障碍物的颜色 2.4.1 创建材质 2.4.2 给跑道和障碍物更换材质 四、功能脚本实现 1. 创建玩家脚本 2. 相机跟随 3. 胜负的判定 3.1 …...
PyQt6 QInputDialog输入对话框控件
锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计50条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话版…...
ASP.NET Core MVC依赖注入理解(极简个人版)
依赖注入 文献来源:《Pro ASP.NET Core MVC》 Adam Freeman 第18章 依赖注入 1 依赖注入原理 所有可能变化的地方都用接口在使用接口的地方用什么实体类通过在ConfigureService中注册解决注册的实体类需要指定在何种生命周期中有效 TransientScopedSingleton 2…...
美光将于 2025 年推出 1γ DRAM,并在日本生产HBM
美国内存巨头美光正准备从 2025 年开始在其位于日本广岛的晶圆厂生产最先进的“1γ”DRAM。同时,公司计划在同一晶圆厂生产高带宽存储器(HBM),以满足对生成式人工智能应用日益增长的需求。 据《日经亚洲》12月13日报道࿰…...
【Docker】以service形式离线安装卸载的docker、compose服务
CentOS7离线卸载Docker步骤 移除开机自启 [rootCenOS-1 system]# systemctl disable docker移除注册文件 rm -rf /etc/systemd/system/docker.service删除相关安装目录 rm -rf $(find / -name docker)CentOS7离线安装Docker、Compose步骤 资源地址:docker_20.10…...
Dubbo RPC-Redis协议
Redis协议 特性说明 Redis 是一个高效的 KV 存储服务器。基于 Redis 实现的 RPC 协议。 2.3.0 以上版本支持。 使用场景 缓存,限流,分布式锁等 使用方式 引入依赖 从 Dubbo 3 开始,Redis 协议已经不再内嵌在 Dubbo 中,需要单…...
展开说说:Android之常用的延时执行策略
总结了以下六种常用的Android延时执行策略,以此记录: 1、TimerTask 2、Handler.postDelayed 3、Handler.sendEnptyMessageDelayeed 4、Thread.sleep线程休眠-需要在子线程 5、使用AlarmManager-全局定时器或者闹钟 6、Wait 首先定义一个时间常量&…...
Jenkins在window下配置Android打包配置
在Windows下配置Jenkins进行Android打包的步骤如下: 安装Jenkins:从Jenkins官网下载适用于Windows的安装包,并按照安装向导的指示完成安装。 启动Jenkins服务:启动Jenkins服务,确保服务正常运行。 配置Jenkins&#…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
