深入理解设计模式:访问者模式详解

在软件开发中,我们经常会遇到需要对一个复杂对象结构进行操作的情况。随着需求的不断变化,我们可能需要在这个对象结构上添加各种新的操作。如果直接在对象结构中添加这些操作,会导致类的职责过重,且每次添加新操作都需要修改原有代码,违反了开闭原则。访问者模式(Visitor Pattern)就是为了解决这类问题而诞生的。

本文将全面介绍访问者模式,包括其定义、结构、实现方式、优缺点、适用场景以及在实际项目中的应用案例,帮助读者深入理解这一重要的设计模式。

一、访问者模式概述

1.1 模式定义

访问者模式是一种行为型设计模式,它允许你将算法与对象结构分离,使得可以在不修改现有对象结构的情况下向对象结构添加新的操作。该模式的核心思想是将数据结构与数据操作分离,从而达到解耦的目的。

1.2 模式特点

访问者模式具有以下显著特点:

  • 将相关操作集中到一个访问者对象中,而不是分散在元素类中

  • 可以方便地添加新的操作,只需增加新的访问者类即可

  • 访问者可以累积状态,这在遍历复杂对象结构时很有用

1.3 模式起源

访问者模式最早由Gang of Four(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)在他们1994年出版的《设计模式:可复用面向对象软件的基础》一书中提出,是23种经典设计模式之一。

二、访问者模式的结构

2.1 UML类图

2.2 模式角色

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

  1. Visitor(访问者接口)

    • 声明了一组访问操作,每个操作对应一个具体元素类

    • 通常为每个具体元素类声明一个visit方法

  2. ConcreteVisitor(具体访问者)

    • 实现Visitor接口声明的操作

    • 每个操作实现算法的一部分,而该算法片段对应于结构中的相应类

  3. Element(元素接口)

    • 定义一个accept方法接受访问者对象

    • 通常是"public void accept(Visitor visitor)"

  4. ConcreteElement(具体元素)

    • 实现Element接口的accept方法

    • 在accept方法中调用访问者的visit方法

  5. ObjectStructure(对象结构)

    • 能够枚举它的元素

    • 可以提供一个高层接口允许访问者访问它的元素

    • 可以是一个组合模式或是一个简单的集合

2.3 模式协作

访问者模式的工作流程如下:

  1. 客户端创建一个具体访问者对象

  2. 客户端通过对象结构的接口遍历所有元素

  3. 客户端将访问者对象传递给每个元素的accept操作

  4. 元素的accept操作调用访问者的visit操作,并将自身作为参数传递

三、访问者模式的实现

3.1 基础实现示例

下面是一个完整的Java实现示例:

// 访问者接口
interface Visitor {void visit(ConcreteElementA element);void visit(ConcreteElementB element);
}// 具体访问者1
class ConcreteVisitor1 implements Visitor {public void visit(ConcreteElementA element) {System.out.println("ConcreteVisitor1处理ConcreteElementA: " + element.operationA());}public void visit(ConcreteElementB element) {System.out.println("ConcreteVisitor1处理ConcreteElementB: " + element.operationB());}
}// 具体访问者2
class ConcreteVisitor2 implements Visitor {public void visit(ConcreteElementA element) {System.out.println("ConcreteVisitor2处理ConcreteElementA: " + element.operationA());}public void visit(ConcreteElementB element) {System.out.println("ConcreteVisitor2处理ConcreteElementB: " + element.operationB());}
}// 元素接口
interface Element {void accept(Visitor visitor);
}// 具体元素A
class ConcreteElementA implements Element {public void accept(Visitor visitor) {visitor.visit(this);}public String operationA() {return "具体元素A的操作";}
}// 具体元素B
class ConcreteElementB implements Element {public void accept(Visitor visitor) {visitor.visit(this);}public String operationB() {return "具体元素B的操作";}
}// 对象结构
class ObjectStructure {private List<Element> elements = new ArrayList<>();public void attach(Element element) {elements.add(element);}public void detach(Element element) {elements.remove(element);}public void accept(Visitor visitor) {for (Element element : elements) {element.accept(visitor);}}
}// 客户端代码
public class Client {public static void main(String[] args) {ObjectStructure objectStructure = new ObjectStructure();objectStructure.attach(new ConcreteElementA());objectStructure.attach(new ConcreteElementB());Visitor visitor1 = new ConcreteVisitor1();objectStructure.accept(visitor1);System.out.println("----------");Visitor visitor2 = new ConcreteVisitor2();objectStructure.accept(visitor2);}
}

3.2 输出结果

ConcreteVisitor1处理ConcreteElementA: 具体元素A的操作
ConcreteVisitor1处理ConcreteElementB: 具体元素B的操作
----------
ConcreteVisitor2处理ConcreteElementA: 具体元素A的操作
ConcreteVisitor2处理ConcreteElementB: 具体元素B的操作

3.3 实现要点

  1. 双分派机制:访问者模式使用了双分派(double dispatch)的技术,即根据请求的类型和接收者的类型来决定使用哪个方法

  2. 元素接口设计:Element接口应该足够通用,以支持各种访问者

  3. 访问者接口设计:Visitor接口应该为每种具体元素类型都声明一个visit方法

  4. 对象结构管理:ObjectStructure负责维护元素集合,并提供遍历接口

四、访问者模式的优缺点

4.1 优点

  1. 开闭原则:可以在不修改现有对象结构的情况下添加新的操作,只需增加新的访问者类

  2. 单一职责原则:将相关行为集中到一个访问者对象中,而不是分散在元素类中

  3. 灵活性:访问者可以累积状态,这在遍历复杂对象结构时很有用

  4. 复用性:可以在不同访问者中复用相同的元素结构

  5. 扩展性:添加新的访问者很容易,不需要修改元素类

4.2 缺点

  1. 破坏封装:访问者可能需要访问元素的内部状态,这可能会破坏元素的封装性

  2. 元素接口变更困难:每增加一个新的具体元素类,都需要修改访问者接口及所有具体访问者类

  3. 对象结构变更困难:如果对象结构经常变化,访问者模式可能不太适用

  4. 复杂性增加:对于简单的对象结构,使用访问者模式可能会增加不必要的复杂性

五、访问者模式的适用场景

访问者模式适用于以下场景:

  1. 复杂对象结构:对象结构中包含许多具有不同接口的对象类,且希望对它们执行依赖于具体类的操作

  2. 多种不相关操作:需要对一个对象结构中的对象进行很多不同且不相关的操作,且不希望这些操作"污染"元素的类

  3. 稳定的数据结构:对象结构很少变化,但经常需要在此结构上定义新的操作

  4. 算法与结构分离:希望将算法与它们操作的对象结构分离

六、访问者模式的实际应用

6.1 编译器设计

在编译器设计中,抽象语法树(AST)是一个典型的复杂对象结构。访问者模式可以用于实现各种AST操作,如类型检查、代码优化、代码生成等。每种操作可以由不同的访问者实现,而不需要修改AST节点类。

// AST节点接口
interface ASTNode {void accept(ASTVisitor visitor);
}// 访问者接口
interface ASTVisitor {void visit(AssignmentNode node);void visit(VariableNode node);void visit(NumberNode node);
}// 类型检查访问者
class TypeCheckVisitor implements ASTVisitor {public void visit(AssignmentNode node) {// 类型检查逻辑}public void visit(VariableNode node) {// 类型检查逻辑}public void visit(NumberNode node) {// 类型检查逻辑}
}// 代码生成访问者
class CodeGenVisitor implements ASTVisitor {public void visit(AssignmentNode node) {// 代码生成逻辑}public void visit(VariableNode node) {// 代码生成逻辑}public void visit(NumberNode node) {// 代码生成逻辑}
}

6.2 文档处理

在XML或JSON文档处理中,可以使用访问者模式来实现各种文档处理操作,如格式转换、内容提取、验证等。

6.3 GUI组件处理

在图形用户界面中,复杂的UI组件树可以使用访问者模式来实现各种操作,如渲染、布局计算、事件处理等。

七、访问者模式与其他模式的关系

  1. 组合模式:访问者模式经常用于处理由组合模式定义的对象结构

  2. 解释器模式:访问者可以用于在抽象语法树上执行操作

  3. 迭代器模式:访问者模式可以看作是一个更复杂的迭代器,它不仅遍历对象结构,还对每个元素执行操作

八、总结

访问者模式是一种强大的行为设计模式,它通过将算法与对象结构分离,提供了在不修改现有类的情况下扩展其功能的能力。虽然它有一些缺点,如可能破坏封装性和增加系统复杂性,但在适当的场景下,访问者模式可以极大地提高代码的灵活性和可维护性。

在实际应用中,访问者模式特别适合于那些数据结构稳定但操作频繁变化的系统。当我们需要对复杂对象结构执行多种不相关的操作时,访问者模式可以有效地组织代码,避免"操作污染"元素类。

理解并合理运用访问者模式,可以帮助我们设计出更加灵活、可扩展的软件系统。

 

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

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

相关文章

Linux timerfd 定时器封装

使用 timerfd epoll() 实现&#xff0c;简洁精确。没定义 MU_ERROR 宏的话替换为 printf 即可。mu_timer.h:#ifndef _MU_TIMER_H_ #define _MU_TIMER_H_#ifdef __cplusplus extern "C" { #endif#include <stdint.h> #include <time.h> #include <pth…

【样式效果】Vue3实现仿制iOS按钮动态效果

iOS开关效果定义变量&#xff1a; <style scoped lang"scss">.layout {// 按钮宽度$button-width: 500px;// 按钮高度$button-height: 250px;// 按钮里面圆形直径$circle-diameter: 200px;// 按钮背景与里面圆形间距$button-circle-offset:calc(($button-he…

京东疯狂投资具身智能:众擎机器人+千寻智能+逐际动力 | AI早报

每日分享全球最新AI资讯【应用商业八卦技术】&#xff0c;&#x1f30f;&#xff1a;未来世界2099应用 1、马斯克推出儿童AI"Baby Grok"引热议&#xff1a;安全性能否经受考验&#xff1f; 2、蚂蚁AQ健康应用霸榜苹果商店&#xff0c;或将联手Apple Watch打造智能健康…

Jiasou TideFlow AIGC SEO Agent:全自动外链构建技术重构智能营销新标准

AI时代SEO技术革命&#xff1a;企业如何突破流量增长瓶颈&#xff1f;随着Google算法升级至MUM模型&#xff0c;传统SEO工具已难以应对多模态内容优化需求。在搜索引擎日均处理120亿次查询的生态中&#xff0c;企业官网平均自然流量转化周期长达6-8个月&#xff0c;因此诸如Jia…

Docker-compose:服务编排

Docker-compose 介绍 服务编排:按照一定的业务规则批量管理容器 在微服务架构的应用系统中,一般包含 N 个微服务,且每个微服务一般都会部署多个实例。此时,如果每个微服务都要手动启停,维护的工作量会很大。 要从 Dockerfile build image 或者去 docker hub 拉取 image …

异地服务器备份Mysql数据

前言异地服务器备份Mysql数据即Mysql的server端与备份服务器不是同一个。Mysql服务端安装在192.168.3.36中&#xff0c;现在需要在IP为192.168.209.129的服务器中使用mysqldump命令备份指定数据库数据;192.168.209.129没有装过Mysql客户端;1.安装Mysql客户端不安装Mysql客户端就…

NGINX 高级配置解析:`proxy_request_buffering` 使用详解

在使用 NGINX 作为反向代理服务器时&#xff0c;处理客户端请求体&#xff08;如上传文件或大体积 POST 请求&#xff09;的方式会直接影响应用性能与资源使用。其中&#xff0c;proxy_request_buffering 是一个非常关键但容易被忽略的配置项。 本文将详细介绍该指令的作用、典…

增加交叉验证和超参数调优

前文中&#xff0c;只是给了基础模型&#xff1a; PyTorch 实现 CIFAR-10 图像分类&#xff1a;从数据预处理到模型训练与评估-CSDN博客 今天我们增加交叉验证和超参数调优&#xff0c; 先看运行结果&#xff1a; 在测试集上评估最终模型 最终模型在测试集上的准确率&…

解决pip指令超时问题

用pip指令&#xff0c;在安装Django3.2时报错&#xff0c;询问ChatGpt后得到的解决方案pip 下载超时 —— 是 当前网络连接到 PyPI 官方源太慢或不稳定&#xff0c;甚至可能连不上了&#xff0c;而 pip 默认的超时时间又太短&#xff0c;就导致了中途失败&#xff1a;ReadTimeo…

Oracle定时清理归档日志

线上归档日志满了&#xff0c;系统直接崩了&#xff0c;为解决这个问题&#xff0c;创建每月定时清理归档日志。 创建文件名 delete_archivelog.rman CONFIGURE ARCHIVELOG DELETION POLICY CLEAR; RUN {ALLOCATE CHANNEL c1 TYPE DISK;DELETE ARCHIVELOG ALL COMPLETED BEFORE…

ELF 文件操作手册

目录 一、ELF 文件结构概述 二、查看 ELF 文件头信息 1、命令选项 2、示例输出 3、内核数据结构 三、ELF 程序头表 1、命令选项 2、示例输出 3、关键说明 4、内核数据结构 四、ELF 节头表详解 查看节头表信息 1、命令选项 2、示例输出 3、标志说明 4、重要节说…

深入浅出Python函数:参数传递、作用域与案例详解

&#x1f64b;‍♀️ 博主介绍&#xff1a;颜颜yan_ ⭐ 本期精彩&#xff1a;深入浅出Python函数&#xff1a;参数传递、作用域与案例详解 &#x1f3c6; 热门专栏&#xff1a;零基础玩转Python爬虫&#xff1a;手把手教你成为数据猎人 &#x1f680; 专栏亮点&#xff1a;零基…

ps aux 和 ps -ef

在 Linux/Unix 系统中&#xff0c;ps aux 和 ps -ef 都是用于查看进程信息的命令&#xff0c;结合 grep node 可以筛选出与 Node.js 相关的进程。它们的核心功能相似&#xff0c;但在输出格式和选项含义上有区别&#xff1a;1. 命令对比命令含义主要区别ps auxBSD 风格语法列更…

Spark ML 之 LSH

src/test/scala/org/apache/spark/ml/feature/BucketedRandomProjectionLSHSuite.scala test("approxSimilarityJoin for self join") {val data = {for (i <- 0 until 24) yield Vectors

关键成功因素法(CSF)深度解析:从战略目标到数据字典

关键成功因素法由John Rockart提出&#xff0c;用于信息系统规划&#xff0c;帮助企业识别影响系统成功的关键因素&#xff0c;从而确定信息需求&#xff0c;指导信息技术管理。该方法通过识别关键成功因素&#xff0c;找出关键信息集合&#xff0c;确定系统开发优先级&#xf…

Django母婴商城项目实践(六)- Models模型之ORM操作

6、Models模型操作 1 ORM概述 介绍 Django对数据进行增删改操作是借助内置的ORM框架(Object Relational Mapping,对象关系映射)所提供的API方法实现的,允许你使用类和对象对数据库进行操作,从而避免通过SQL语句操作数据库。 简单来说,ORM框架的数据操作API是在 QuerySet…

【PTA数据结构 | C语言版】哥尼斯堡的“七桥问题”

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 哥尼斯堡是位于普累格河上的一座城市&#xff0c;它包含两个岛屿及连接它们的七座桥&#xff0c;如下图所示。 可否走过这样的七座桥&#xff0c;而且每桥只走过一次&#xff1f;瑞士数学家欧拉(Leo…

Redis 详解:从入门到进阶

文章目录前言一、什么是 Redis&#xff1f;二、Redis 使用场景1. 缓存热点数据2. 消息队列3. 分布式锁4. 限流与防刷5. 计数器、排行榜三、缓存三大问题&#xff1a;雪崩 / 穿透 / 击穿1. ❄️ 缓存雪崩&#xff08;Cache Avalanche&#xff09;2. &#x1f50d; 缓存穿透&…

QCustomPlot 使用教程

下载网址&#xff1a;官方网站&#xff1a;http://www.qcustomplot.com/我的环境是 window10 qt5.9.9 下载后&#xff0c;官网提供了很多例子。可以作为参考直接运行自己如何使用&#xff1a;第一步&#xff1a;使用QCustomPlot非常简单&#xff0c;只需要把qcustomplot.cpp和…

基于springboot+mysql的作业管理系统(源码+论文)

一、开发环境 1 Spring Boot框架简介 描述&#xff1a; 简化开发&#xff1a;Spring Boot旨在简化新Spring应用的初始搭建和开发过程。配置方式&#xff1a;采用特定的配置方式&#xff0c;减少样板化配置&#xff0c;使开发人员无需定义繁琐的配置。开发工具&#xff1a;可…