当前位置: 首页 > article >正文

23种设计模式-行为型模式-访问者

文章目录

  • 简介
  • 场景
  • 解决
    • 完整代码
    • 核心实现
  • 总结

简介

访问者是一种行为设计模式,它能把算法跟他所作用的对象隔离开来。

场景

假如你的团队开发了一款能够使用图像里地理信息的应用程序。图像中的每个节点既能代表复杂实体(例如一座城市),也能代表更精细的对象(例如工业区和旅游景点等)。每个节点的类型都由它所属的类来表示,每个特定的节点就是一个对象。

将图像导出为  XML。
现在,你需要把图像导出到 XML 文件里。你打算为每个节点类添加导出函数,然后递归执行图像中每个节点的导出函数,有点类似我们之前的组合模式。另外,还可以使用多态让导出方法的调用代码不和具体的节点类相耦合。
但其他同事并不想修改已有的节点类。他们认为这些代码已经是产品了,修改的话可能会引入缺陷。
所有节点的类里都必须添加导出至 XML 文件的方法,但如果在修改代码的过程中引入了任何缺陷,那么整个程序都会面临风险。
另外,他们觉得在节点类里包含导出 XML 文件的代码好像没有意义。这些类的主要工作是处理地理数据。加入导出 XML 文件的代码明显不合适。
还有,这个功能完成之后,很有可能还需要提供导出其他类型文件的功能,或者其他奇怪的功能。这样你很可能要再次修改这些脆弱的类。

解决

访问者模式建议把新功能(行为)放在一个叫做访问者的独立类里,而不是整合到已有类里。需要执行操作的原始对象将作为参数传给访问者的方法,让方法能访问对象所包含的一切必要数据。
对于不同类的对象(比如示例里不同的节点),该怎么操作? 在我们的示例里,不同节点类导出 XML 文件的实际实现很可能不同。因此,访问者类可以定义一组方法,每个方法可接收特定类型的参数,如下所示:

class ExportVisitor implements Visitor {method doForCity(City c) { ... }method doForIndustry(Industry f) { ... }method doForSightSeeing(SightSeeing ss) { ... }

但我们究竟应该怎么调用这些方法(尤其是在处理整个图像方面)呢?这些方法的签名各不相同,我们就不能用多态机制。为了可以挑选出能够处理特定对象的访问者方法,我们需要先对它的类进行检查。是不是很麻烦?

foreach (Node node in graph)if (node instanceof City)exportVisitor.doForCity((City) node)if (node instanceof Industry)exportVisitor.doForIndustry((Industry) node)
}

为什么不用方法重载?即用相同的方法名称,不同的参数。 我们无法提前知道节点对象所属的类,所以重载机制没办法执行正确的方法。因为方法会把节点基类作为输入参数的默认类型。
访问者模式可以解决这个问题。它使用了一种叫做双分派的技巧,不使用麻烦的条件语句也可以执行正确的方法。其实,与其让客户端来选择调用特定的方法,不如把选择权委派给 作为参数传递给访问者的对象。因为这个对象知道它自己的类,能更自然地在访问者中选出正确的方法。它们会“接收”一个访问者并且告诉它应该执行的访问者方法。

// Client code
foreach (Node node in graph)node.accept(exportVisitor)
// City
class City ismethod accept(Visitor v) isv.doForCity(this)// ...
// Industry
class Industry ismethod accept(Visitor v) isv.doForIndustry(this)// ...

我们还是修改了节点类,但改动很小,而且我们能在后续添加行为时 不需要再次修改节点类的代码。
现在,如果我们抽取出所有访问者的通用接口,所有已有的节点都能跟任何访问者交互了。如果需要引入跟节点相关的某个行为,你只需要实现一个新的访问者类就行了。

完整代码

// Visitor 接口定义地理信息操作规范
interface GeoVisitor {void visitCity(CityElement city);     // 城市节点访问接口 void visitIndustry(IndustryElement industry); // 工业区访问接口void visitSightseeing(SightseeingElement sight); // 景区访问接口
}// 具象访问者(统计信息导出)
class StatisticsExporter implements GeoVisitor {@Overridepublic void visitCity(CityElement city) {  // 处理城市数据System.out.printf("统计城市[%s]: 人口%d万 | ",city.getName(), city.getPopulation());  // 访问节点属性System.out.println("GDP " + city.getGDP() + "亿元");}@Override public void visitIndustry(IndustryElement industry) {  // 处理工业区System.out.printf("工业区评估: %s 类型 | 占地面积%.1f平方公里\n",industry.getIndustryType(), industry.getArea());}@Overridepublic void visitSightseeing(SightseeingElement sight) {  // 处理景点System.out.printf("景点分级: %s(%dA景区)\n",sight.getLandmark(), sight.getRating());}
}// 地理元素抽象接口(使用双分派机制)
interface GeoElement {void accept(GeoVisitor visitor);  // 关键接收方法 
}// 城市实体类
class CityElement implements GeoElement {private String name;private int population;private double gdp;public CityElement(String name, int pop, double gdp) { ... } @Overridepublic void accept(GeoVisitor visitor) {  // 双分派入口 visitor.visitCity(this);}// Getters省略
}// 工业园区实体类 
class IndustryElement implements GeoElement {private String industryType;private double area;public IndustryElement(String type, double area) { ... }@Overridepublic void accept(GeoVisitor visitor) {visitor.visitIndustry(this);  // 调用工业区专门方法}
}// 旅游景区实体类
class SightseeingElement implements GeoElement {private String landmark;private int rating;public SightseeingElement(String name, int stars) { ... }@Override public void accept(GeoVisitor visitor) {visitor.visitSightseeing(this); // 调用景区处理逻辑}
}// 客户端使用
public class GeoClient {public static void main(String[] args) {// 构建地理数据模型(复杂对象结构)List<GeoElement> geoGraph = Arrays.asList(new CityElement("上海", 2487, 43214),new IndustryElement("化工", 58.3),new SightseeingElement("外滩", 5));// 应用统计访问者GeoVisitor exporter = new StatisticsExporter();geoGraph.forEach(element -> element.accept(exporter));  // 统一访问入口 }
}

核心实现

  1. 双分派机制:通过element.accept()调用visitor特定方法
  2. 开放扩展:新增数据操作只需添加访问者类
  3. 数据结构稳定:元素类无需修改即可支持新业务逻辑

总结

在这里插入图片描述

  1. 访问者(Vis­i­tor)接口:声明了一系列以Concrete Element为参数的访问者方法。如果编程语言支持重载,这些方法的名称可以是相同的,但是他们的参数一定是不同的。
  2. 具体访问者(Con­crete Vis­i­tor)会为不同的Concrete Element实现相同行为的几个不同版本。
  3. 元素(Ele­ment)接口声明了一个方法来“接收”访问者。这个方法必须有一个参数被声明为访问者接口类型。
  4. 具体元素(Con­crete Ele­ment)必须实现接收方法。这个方法的目的是根据当前元素类把调用重定向到相应访问者的方法里。请注意,即使元素基类实现了这个方法,所有子类都必须对它进行重写并调用访问者对象中的合适方法。
  5. 客户端(Client)通常会作为集合或其他复杂对象(例如一个组合树)的代表。客户端通常不知道所有的具体元素类,因为它们会通过抽象接口与集合中的对象进行交互。

相关文章:

23种设计模式-行为型模式-访问者

文章目录 简介场景解决完整代码核心实现 总结 简介 访问者是一种行为设计模式&#xff0c;它能把算法跟他所作用的对象隔离开来。 场景 假如你的团队开发了一款能够使用图像里地理信息的应用程序。图像中的每个节点既能代表复杂实体&#xff08;例如一座城市&#xff09;&am…...

WebView2最低支持.NET frame4.5,win7系统

WebView2最低支持.NET frame什么版本 ‌WebView2 对 .NET Framework 的最低版本要求‌ ‌基础支持范围‌ WebView2 官方支持的 .NET Framework ‌最低版本为 4.5‌&#xff0c;同时兼容 ‌.NET Core 3.0‌ 及以上版本‌18。对于 WPF、WinForms 等桌面应用开发&#xff0c;需确…...

WHAT - React 组件的 props.children 属性

目录 一、什么是 children二、基本用法三、类型定义&#xff08;TypeScript&#xff09;四、一些高级用法1. 条件渲染 children2. 多个 children 插槽&#xff08;命名插槽&#xff09; 五、children 的优势总结 在 React 中&#xff0c;children 是一个非常重要且特殊的 内置属…...

组播网络构建:IGMP、PIM 原理及应用实践

IP组播基础 组播基本架构 组播IP地址 一个组播IP地址并不是表示具体的某台主机&#xff0c;而是一组主机的集合&#xff0c;主机声明加入某组播组即标识自己需要接收目的地址为该组播地址的数据IP组播常见模型分为ASM模型和SSM模型ASM&#xff1a;成员接收任意源组播数据&…...

建筑兔零基础自学记录69|爬虫Requests-2

Requests库初步尝试 #导入requests库 import requests #requests.get读取百度网页 rrequests.get(http://www.baidu.com) #输出读取网页状态 print(r.status_code) #输出网页源代码 print(r.text) HTTP 状态码是三位数字&#xff0c;用于表示 HTTP 请求的结果。常见的状态码有…...

NVIDIA PhysX 和 Flow 现已完全开源

NVIDIA PhysX SDK 在 3-Clause BSD 许可下开源已有六年半了&#xff0c;但其中并非所有内容都是开源的。直到最近&#xff0c;随着 GPU 模拟内核源代码在 GitHub 上的发布&#xff0c;这种情况才有所改变。以下是 NVIDIA 分享的消息&#xff0c;以及 Flow SDK 着色器实现的发布…...

QML面试笔记--UI设计篇01常用控件分类

1. QML常用控件深度解析&#xff1a;从入门到实战2. 控件分类全景图3. 核心控件详解 3.1. 布局控件&#xff08;构建界面骨架&#xff09; 3.1.1. ▶ ColumnLayout 3.2. 交互控件 3.2.1. ▶ 智能搜索框&#xff08;组合控件&#xff09; 3.3. 数据可视化控件 3.3.1. ▶ 动态仪表…...

电脑DNS出错无法打开网页

目录 解决步骤 打开“控制面板”--》“查看网络状态和任务” 打开“更改适配器设置” 对WLAN右键&#xff0c;打开属性 打开“使用下面的DNS服务器地址”--》高级 添加“114.114.114.114”&#xff0c;点击确定 今天晚上突然网页打不开了&#xff0c;一开始我以为是网络的…...

[Redis]redis-windows下载安装与使用

本篇记录windows redis下载安装与使用。 下载 官网下载方式(没windows版) https://redis.io/downloads/#stack 可以选择下载社区版Redis CE与增强版Redis Stack。 两者都不支持直接运行在windows上&#xff0c;需要Docker环境。 You can install Redis CE locally on your …...

Python-Django+vue宠物服务管理系统功能说明

❥(^_-) 上千个精美定制模板,各类成品Java、Python、PHP、Android毕设项目,欢迎咨询。 ❥(^_-) 程序开发、技术解答、代码讲解、文档,💖文末获取源码+数据库+文档💖 💖软件下载 | 实战案例 💖文章底部二维码,可以联系获取软件下载链接,及项目演示视频。 本项目…...

极氪汽车云原生架构落地实践

云原生架构落地实践的背景 随着极氪数字业务的飞速发展&#xff0c;背后的 IT 技术也在不断更新迭代。极氪极为重视客户对服务的体验&#xff0c;并将系统稳定性、业务功能的迭代效率、问题的快速定位和解决视为构建核心竞争力的基石。 为快速响应用户的需求&#xff0c;例如…...

2025年AI开发学习路线

目录 一、基础阶段&#xff08;2-3个月&#xff09; 1. 数学与编程基础 2. 机器学习入门 二、核心技能&#xff08;3-4个月&#xff09; 1. 深度学习与框架 2. 大模型开发&#xff08;重点&#xff09; 三、进阶方向&#xff08;3-6个月&#xff09; 1. 多模态与智能体…...

网络出故障时,四大表(MAC表、ARP表、路由表、转发表)怎么查?看看这套排查顺序

网络出故障时&#xff0c;四大表 (MAC表、ARP表、路由表、转发表) 怎么查 说正题之前&#xff0c;我们先来假设一个场景&#xff1a; 场景假设&#xff1a; 一台华为设备突然上不了网&#xff0c;或者访问某个 IP 不通。 你会怎么排查&#xff1f; 别慌&#xff0c;兄弟&a…...

数据结构与算法-图论-复习1(单源最短路,全源最短路,最小生成树)

1. 单源最短路 单一边权 BFS 原理&#xff1a;由于边权为单一值&#xff0c;可使用广度优先搜索&#xff08;BFS&#xff09;来求解最短路。BFS 会逐层扩展节点&#xff0c;由于边权相同&#xff0c;第一次到达某个节点时的路径长度就是最短路径长度。 用法&#xff1a;适用…...

oracle 动态性能视图

Oracle 数据库中的 V$SQLAREA 是一个动态性能视图&#xff08;Dynamic Performance View&#xff09;&#xff0c;用于记录共享池&#xff08;Shared Pool&#xff09;中所有 SQL 语句的统计信息。每个 SQL 语句在共享池中存储为一个游标&#xff08;Cursor&#xff09;&#x…...

Vue3+Vite+TypeScript+Element Plus开发-10.多用户动态加载菜单

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 Header响应式菜单缩展 Mockjs引用与Axios封装 登录设计 登录成功跳转主页 多用户动态加载菜单 Pinia持久化 动态路由-配置 文章目录 目录 系列文档目…...

前端用户列表与后端分页协同设计

分页实现方案 在现代Web应用中&#xff0c;用户列表展示与分页是一个常见的功能需求。前端与后端通过API协同工作&#xff0c;使用PageHelper等工具实现高效分页。 例如&#xff1a; 后端实现 (使用PageHelper) public PageResult DishPage(DishPageQueryDTO dishPageQuery…...

三月份面试感触

我毕业三年了&#xff0c;也在公司干了三年本来还以为很快的找到工作&#xff0c;没想到呀现在就业环境是真的差&#xff0c;那个boss和智联一堆的外包找你&#xff0c;找你要了简历然后就没下文了&#xff0c;我也去面了几家自研的公司&#xff0c;只能说这不是欺负老实人吗&a…...

C++使用WebView2控件,通过IPC通信与Javascript交互

引言 在现代桌面应用程序开发中&#xff0c;Web技术与原生应用的融合变得越来越普遍。Microsoft的WebView2控件为C开发者提供了一个强大的工具&#xff0c;使他们能够在桌面应用中嵌入基于Chromium的Web浏览器引擎。本文将详细介绍如何在C应用程序中使用WebView2控件&#xff…...

精准测试建设过程中遇到的一些问题

1.sqlite3 仅可以处理单个任务问题&#xff0c;多线程往往会面临数据库锁定 因为仅临时存储&#xff0c;后来在创建数据库时&#xff0c;给每个任务开了一个临时数据库&#xff0c;存储数据执行完毕后&#xff0c;删除db sql_insert_new:INSERT INTO analyze_api_resault_dynam…...

【Docker】Dockerfile 编写实践

&#x1f47b;创作者&#xff1a;丶重明 &#x1f47b;创作时间&#xff1a;2025年4月8日 &#x1f47b;擅长领域&#xff1a;运维 目录 1. Dockerfile编写原则1.1.选择合适的基础镜像1.2.镜像层优化1.3.多阶段构建1.4.安全增强 2. 关键指令与技巧2.1.COPY vs ADD2.2.ENTRYPOIN…...

Jakarta EE 11发布:云原生Java企业应用的新标准

&#x1f4dd; 摘要 Jakarta EE 11于2023年正式发布&#xff0c;这是Java企业版技术栈的一次重要更新。本文将详细介绍Jakarta EE 11的核心特性、改进之处以及如何利用这些新功能构建现代化的云原生应用。我们将通过实际代码示例展示新特性的使用方法&#xff0c;并分析其对Ja…...

蓝桥杯第十五届C++B组省赛真题解析

蓝桥杯第十五届CB组省赛真题解析 一、宝石组合https://www.lanqiao.cn/problems/19711/learning/ 解题思路 题目要求找到三个数&#xff0c;使得它们的最大公约数&#xff08;GCD&#xff09;尽可能大&#xff0c;并在GCD相同的情况下选择数值最小的三个数。以下是分步解析&a…...

LabVIEW商业软件开发注意问题

在 LabVIEW 商业软件开发进程中&#xff0c;性能优化、界面设计及兼容性与扩展性&#xff0c;对软件品质、用户体验和市场适配性起着决定性作用。下面&#xff0c;借助多个LabVIEW 编程特性的实际案例&#xff0c;深入分析这些方面的开发要点。 一、性能优化&#xff1a;提升软…...

面试算法高频04-分治与回溯

分治与回溯 分治和回溯算法&#xff0c;包括其概念、特性、代码模板&#xff0c;并结合具体题目进行讲解&#xff0c;旨在帮助学员理解和掌握这两种算法的应用。 分治与回溯的概念 分治&#xff08;Divide & Conquer&#xff09;&#xff1a;本质上基于递归&#xff0c;先…...

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法 报错内容尝试第一次解决方法尝试第二次解决方法注意事项参考连接 报错内容 Unable to download server on client side: Error: Request downloadRequest failed unexpectedly without providing any details… Will tr…...

Java 中 SQL 注入问题剖析​

一、引言​ 在当今数字化时代&#xff0c;数据是企业和组织的核心资产之一。许多应用程序都依赖于数据库来存储和管理数据&#xff0c;而 Java 作为一种广泛使用的编程语言&#xff0c;常被用于开发与数据库交互的应用程序。然而&#xff0c;SQL 注入这一安全漏洞却如同隐藏在…...

华为数字芯片机考2025合集2已校正

单选 1. 题目内容 关于亚稳态的描述错误的是&#xff08; &#xff09;。 1. 解题步骤 1.1 理解亚稳态&#xff08;Metastability&#xff09;的核心特性 亚稳态是指触发器无法在指定时间内稳定输出有效逻辑电平&#xff08;0或1&#xff09;的状态&#xff0c;其关键特点…...

Leedcode刷题 | Day27_贪心算法01

一、学习任务 455.分发饼干代码随想录376. 摆动序列53. 最大子序和 二、具体题目 1.455分发饼干455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对…...

深度学习项目--分组卷积与ResNext网络实验探究(pytorch复现)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 ResNext是分组卷积的开始之作&#xff0c;这里本文将学习ResNext网络&#xff1b;本文复现了ResNext50神经网络&#xff0c;并用其进行了猴痘病分类实验…...