设计模式 22 访问者模式 Visitor Pattern

news2025/1/13 2:42:11
设计模式 22 访问者模式 Visitor Pattern

1.定义


访问者模式是一种行为型设计模式,它允许你在不改变已有类结构的情况下,为一组对象添加新的操作。它将算法与对象结构分离,使你能够在不修改现有类的情况下,为这些类添加新的操作。

2.内涵

访问者模式核心概念,有以下几点:

  1. 访问者 (Visitor): 定义了一个访问具体元素的方法,每个方法对应一个具体元素类。
  2. 具体访问者 (ConcreteVisitor): 实现访问者接口,定义对具体元素类的访问逻辑。
  3. 元素 (Element): 定义一个 accept 方法,接受访问者对象并调用访问者的 visit 方法。
  4. 具体元素 (ConcreteElement): 实现元素接口,提供访问者访问其内部状态的方法。

相关UML 图如下所示:

        

解释:

  • Visitor 接口:定义了 visit 方法,接受一个 Element 对象作为参数。
  • ConcreteVisitor 类:实现了 Visitor 接口,并定义了对 Element 对象的具体操作逻辑。
  • Element 接口:定义了 accept 方法,接受一个 Visitor 对象作为参数,并调用访问者的 visit 方法。
  • ConcreteElement 类:实现了 Element 接口,并提供 operation 方法,用于访问其内部状态。

3.案例分析
/**
 * The Visitor Interface declares a set of visiting methods that correspond to
 * component classes. The signature of a visiting method allows the visitor to
 * identify the exact class of the component that it's dealing with.
 */
class ConcreteComponentA;
class ConcreteComponentB;

class Visitor {
 public:
  virtual void VisitConcreteComponentA(const ConcreteComponentA *element) const = 0;
  virtual void VisitConcreteComponentB(const ConcreteComponentB *element) const = 0;
};

/**
 * The Component interface declares an `accept` method that should take the base
 * visitor interface as an argument.
 */

class Component {
 public:
  virtual ~Component() {}
  virtual void Accept(Visitor *visitor) const = 0;
};

/**
 * Each Concrete Component must implement the `Accept` method in such a way that
 * it calls the visitor's method corresponding to the component's class.
 */
class ConcreteComponentA : public Component {
  /**
   * Note that we're calling `visitConcreteComponentA`, which matches the
   * current class name. This way we let the visitor know the class of the
   * component it works with.
   */
 public:
  void Accept(Visitor *visitor) const override {
    visitor->VisitConcreteComponentA(this);
  }
  /**
   * Concrete Components may have special methods that don't exist in their base
   * class or interface. The Visitor is still able to use these methods since
   * it's aware of the component's concrete class.
   */
  std::string ExclusiveMethodOfConcreteComponentA() const {
    return "A";
  }
};

class ConcreteComponentB : public Component {
  /**
   * Same here: visitConcreteComponentB => ConcreteComponentB
   */
 public:
  void Accept(Visitor *visitor) const override {
    visitor->VisitConcreteComponentB(this);
  }
  std::string SpecialMethodOfConcreteComponentB() const {
    return "B";
  }
};

/**
 * Concrete Visitors implement several versions of the same algorithm, which can
 * work with all concrete component classes.
 *
 * You can experience the biggest benefit of the Visitor pattern when using it
 * with a complex object structure, such as a Composite tree. In this case, it
 * might be helpful to store some intermediate state of the algorithm while
 * executing visitor's methods over various objects of the structure.
 */
class ConcreteVisitor1 : public Visitor {
 public:
  void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
    std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
  }

  void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
    std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
  }
};

class ConcreteVisitor2 : public Visitor {
 public:
  void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
    std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
  }
  void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
    std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
  }
};
/**
 * The client code can run visitor operations over any set of elements without
 * figuring out their concrete classes. The accept operation directs a call to
 * the appropriate operation in the visitor object.
 */
void ClientCode(std::array<const Component *, 2> components, Visitor *visitor) {
  // ...
  for (const Component *comp : components) {
    comp->Accept(visitor);
  }
  // ...
}

int main() {
  std::array<const Component *, 2> components = {new ConcreteComponentA, new ConcreteComponentB};
  std::cout << "The client code works with all visitors via the base Visitor interface:\n";
  ConcreteVisitor1 *visitor1 = new ConcreteVisitor1;
  ClientCode(components, visitor1);
  std::cout << "\n";
  std::cout << "It allows the same client code to work with different types of visitors:\n";
  ConcreteVisitor2 *visitor2 = new ConcreteVisitor2;
  ClientCode(components, visitor2);

  for (const Component *comp : components) {
    delete comp;
  }
  delete visitor1;
  delete visitor2;

  return 0;
}

输出:

The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1

It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2

上述代码类之间关系 UML图,如下所示


4.注意事项

访问者模式在实施过程中,需要考虑以下几个方面,以确保代码的正确性、可维护性和可扩展性:

1. 元素接口的设计:

元素接口应该定义一个 accept 方法,用于接受访问者对象。
accept 方法应该调用访问者的 visit 方法,并将自身作为参数传递给 visit 方法。
元素接口的设计应该尽可能通用,以支持不同类型的元素。


2. 访问者接口的设计:

访问者接口应该定义一个或多个 visit 方法,每个方法对应一个具体元素类。
visit 方法应该接受对应元素类对象作为参数,并执行对该元素类的操作。
访问者接口的设计应该尽可能灵活,以支持不同的操作。


3. 具体访问者类的实现:

每个具体访问者类应该实现访问者接口,并定义对不同元素类的操作逻辑。
具体访问者类的实现应该尽可能清晰、简洁,以提高代码可读性和可维护性。


4. 访问者模式的应用场景:

访问者模式适用于需要为一组对象添加新的操作,而不想修改这些对象的类的情况。
访问者模式也适用于需要对一组对象进行不同的操作,而这些操作之间没有明显的关联的情况。
访问者模式还可以用于将算法逻辑与对象结构分离,提高代码的可读性和可维护性。


5. 访问者模式的局限性:

访问者模式可能会增加代码的复杂性,尤其是当需要处理多种类型的元素时。
访问者模式可能会破坏元素类的封装性,因为访问者需要访问元素类的内部状态。

5.最佳实践


访问者模式是一种强大的工具,但使用不当会导致代码复杂化或破坏封装性。以下是一些最佳实践,帮助你有效地使用访问者模式:

1. 限制访问者数量:

避免过度使用访问者,只在需要为一组对象添加新操作,且不希望修改这些对象本身时使用。
尽量保持访问者数量较少,否则会增加代码复杂度,难以维护。


2. 保持访问者职责单一:

每个访问者应该只负责一种操作,避免将多个操作混杂在一个访问者中。
职责单一的访问者更容易理解和维护,也更容易进行扩展。


3. 避免访问者修改元素状态:

访问者应该主要负责执行操作,而不是修改元素状态。
如果需要修改元素状态,应该通过元素本身的方法来进行,而不是通过访问者。


4. 使用泛型或模板:

对于需要处理多种类型元素的访问者,可以使用泛型或模板来简化代码。
泛型或模板可以避免重复代码,提高代码可读性和可维护性。


5. 考虑使用组合模式:

如果需要对多个层次结构的元素进行操作,可以考虑将访问者模式与组合模式结合使用。
组合模式可以帮助你构建树形结构,而访问者模式可以帮助你遍历树形结构并执行操作。


6. 谨慎使用访问者模式:

访问者模式并非万能的,在某些情况下,其他设计模式可能更适合。
仔细分析你的需求,选择最合适的模式来解决问题。


6.总结

        访问者模式是一种强大的设计模式,但需要谨慎使用。在实施访问者模式时,需要仔细考虑元素接口和访问者接口的设计,以及具体访问者类的实现。同时,需要了解访问者模式的局限性,并选择合适的应用场景。

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

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

相关文章

922. 按奇偶排序数组 II - 力扣

1. 题目 给定一个非负整数数组 nums&#xff0c; nums 中一半整数是 奇数 &#xff0c;一半整数是 偶数 。 对数组进行排序&#xff0c;以便当 nums[i] 为奇数时&#xff0c;i 也是 奇数 &#xff1b;当 nums[i] 为偶数时&#xff0c; i 也是 偶数 。 你可以返回 任何满足上述…

大学生简历写作指南:让你的简历脱颖而出

在求职过程中&#xff0c;简历不仅是展示自己的镜子&#xff0c;更是赢得面试机会的敲门砖。本文将从简历排版、专业简历定制、内容筛选等方面&#xff0c;提供全面的指导&#xff0c;帮助打造一份既有深度又接地气的简历。 一、简历排版 1.1 根据岗位要求调整排版 准备简历…

LeetCode215数组中第K个最大元素

题目描述 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 解析 快速排序的思想&#xff…

【机器学习】【深度学习】批量归一化(Batch Normalization)

概念简介 归一化指的是将数据缩放到一个固定范围内&#xff0c;通常是 [0, 1]&#xff0c;而标准化是使得数据符合标准正态分布。归一化的作用是使不同特征具有相同的尺度&#xff0c;从而使模型训练更加稳定和快速&#xff0c;尤其是对于使用梯度下降法的算法。而标准化的作用…

Pytorch环境配置2.0.1+ Cuda11.7

查找cuda、cudnn、Pytorch(GPU)及cuda和NVIDIA显卡驱动对应关系 查询可支持的最高cuda版本 nvidia-smi查看支持的cuda的版本 CUDA版本对应表 我的显卡驱动是Driver Version&#xff1a;535.40.&#xff0c;那么左边对应的CUDA都可以兼容 右上角为CUDA 版本&#xff0c;可以看…

OTFS系统建模、通信性能分析、信道估计、模糊函数【附MATLAB代码】

文献来源&#xff1a;​微信公众号&#xff1a;EW Frontier OTFS简介 OTFS信道估计 % Clear command window, workspace variables, and close all figures clc; clear all; close all; ​ % Define Eb values in dB EbdB -10:2:10; ​ % Convert Eb values from dB to lin…

【计算机毕业设计】基于SSM++jsp的汽车客运站管理系统【源码+lw+部署文档】

目录 第1章 绪论 1.1 课题背景 1.2 课题意义 1.3 研究内容 第2章 开发环境与技术 2.1 MYSQL数据库 2.2 JSP技术 2.3 SSM框架 第3章 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信息流…

【iOS】didReceiveMemoryWarning实例方法

iPhone下每个App可用的内存是被限制的&#xff0c;如果一个App使用的内存超过20M&#xff0c;则系统会向该App发送Memory Warning&#xff08;内存警告&#xff09;消息&#xff0c;收到此消息后&#xff0c;App必须正确处理&#xff0c;否则可能出错或出现内存泄漏。 目录 流程…

查询DQL

016条件查询之等量关系 条件查询语法格式 select ... from... where过滤条件;等于 select empno, ename from emp where sal3000;select job, sal from emp where enameFORD;select grade, losal, hisal from salgrade where grade 1;不等于 <> 或 ! selectempno,en…

JS逆向之企名科技

文章目录 初步分析定位js编写完整代码参考文献初步分析 目标网址:企名科技 抓包分析,发现是post请求 请求代码如下: #!/usr/bin/env python3 # -*- coding: utf-8 -*- import requestsheaders = {Connection:

MySQL 数据类型和搜索引擎

文章目录 【 1. 数据类型 】1.1 数值类型1.1.1 整型1.1.2 小数1.1.3 数值类型的选择 1.2 日期和时间YEAR 年TIME 时间DATE 日期DATETIME 日期时间TIMESTAMP 时间戳日期和时间的选择 1.3 文本字符串CHAR 固定字符串、VARCHAR 可变字符串TEXT 文本ENUM 枚举SET 集合字符串类型的选…

错误提示:“由于找不到steam_api.dll,无法继续执行代码”修复方法,缺少steam_api.dll文件原因

在尝试运行某些游戏或程序时&#xff0c;用户可能会遇到一个常见的错误提示&#xff1a;“由于找不到steam_api.dll&#xff0c;无法继续执行代码”。这个错误信息表明&#xff0c;系统在启动程序或游戏时无法定位到必要的steam_api.dll文件&#xff0c;这是一个关键的动态链接…

SwiftUI初探

SwiftUI 虽然出现了好几年(1.0好像2019年出的&#xff0c;还有SPM也是同一年)&#xff0c;现在已经到从1.0到5.0&#xff0c;但受限于对系统的要求(最低iOS13.0,有的要求17.0及以上)&#xff0c;每个版本里面差异也很大&#xff0c;语法和Flutter 的Dart 比较像。空闲之余可以先…

黑马es0-1实现自动补全功能

1、安装分词器 上github上找人做好的分词器&#xff0c;放到es-plugin数据卷里&#xff0c;然后重启es即可 2、自定义分词器 elasticsearch中分词器(analyzer)的组成包含三部分: character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符 …

【Python编程实战】基于Python语言实现学生信息管理系统

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

若依ruoyi-vue element-ui 横向滚动条 动态横向滚动条

动态横向滚动条 因为每次横向滑动都要到底部&#xff0c;引入插件 https://github.com/mizuka-wu/el-table-horizontal-scroll //动态横向滚动条移入样式 .el-table-horizontal-scrollbar :hover{//高度 变大10%transform: scaleY(1.5) translateY(-10%);//百分之八十亮度&a…

阻塞、非阻塞、同步与异步IO的区别

IO读取数据的过程 如图所示&#xff0c;进程读取数据的过程主要分为两个步骤 1.内核将数据准备好到内核缓冲区 2.内核将数据拷贝到用户态 在上述这两个过程里&#xff0c;进程首先和内核打交道&#xff0c;之后内核再和硬件&#xff08;如网卡&#xff09;打交道 阻塞IO 如图所…

将 KNX 接入 Home Assistant 之二 准备软件

写在前面&#xff1a; 在KNX官网也有关于 Home Assistant 的教程&#xff0c;地址是 Get started with Home Assistant x KNX 需要的东西是 a KNX IP Interface or Routera Raspberry Pian SD Card at least 32 GB 安装 Home Assistant 系统 下载镜像&#xff1a; 地址&…

【调试笔记-20240522-Windows-WSL 修改已安装发行版名称】

调试笔记-系列文章目录 调试笔记-20240522-Windows-WSL 修改已安装发行版名称 文章目录 调试笔记-系列文章目录调试笔记-20240522-Windows-WSL 修改已安装发行版名称 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤方法一&#xff1a;修…