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

设计模式 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.总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Autosar Dcm配置-特定NRC实现方式-基于ETAS软件

文章目录 前言工具配置代码编写总结 前言 项目开发过程中&#xff0c;诊断服务一般客户需求或系统需求都会有特定NRC(一般为NRC22-条件不满足)&#xff0c;也就会有特定的条件&#xff0c;需要手动加代码实现。本文介绍ETAS工具中配置的接口及简单实现。 工具配置 对于每一个…

【高阶数据结构】 B树 -- 详解

一、常见的搜索结构 适合做内查找&#xff1a; 以上结构适合用于数据量相对不是很大&#xff0c;能够一次性存放在内存中&#xff0c;进行数据查找的场景。如果数据量很大&#xff0c;比如有 100G 数据&#xff0c;无法一次放进内存中&#xff0c;那就只能放在磁盘上了。 如果…

坦克飞机大战项目详解:从包结构到测试发布

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、项目初始化与包结构构建 代码案例&#xff1a; 二、资源文件与配置文件管理 代码案例…

关于NLTK

一、NLTK简介 下图来自NLTK官网&#xff1a;https://www.nltk.org/index.html NLTK&#xff0c;全称为Natural Language Toolkit&#xff0c;是一个用于处理和分析自然语言文本的Python库。它提供了一系列丰富的工具和资源&#xff0c;包括词汇资源&#xff08;如WordNet&am…

【接口自动化_05课_Pytest接口自动化简单封装与Logging应用】

一、关键字驱动--设计框架的常用的思路 封装的作用&#xff1a;在编程中&#xff0c;封装一个方法&#xff08;函数&#xff09;主要有以下几个作用&#xff1a;1. **代码重用**&#xff1a;通过封装重复使用的代码到一个方法中&#xff0c;你可以在多个地方调用这个方法而不是…

【进程空间】通过页表寻址的过程

文章目录 前言介绍页表、页框、页目录的概念页框页表页目录页表和页目录的分配 一级页表和二级页表一级页表寻址过程 二级页表寻址过程 一级页表和二级页表的对比 前言 我们知道每个进程都有属于自己的虚拟地址空间&#xff0c;且每个进程的虚拟地址都是统一的。要想通过虚拟地…

JS逆向之企名科技

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

【主流分布式算法总结】

文章目录 分布式常见的问题常见的分布式算法Raft算法概念Raft的实现 ZAB算法Paxos算法 分布式常见的问题 分布式场景下困扰我们的3个核心问题&#xff08;CAP&#xff09;&#xff1a;一致性、可用性、分区容错性。 1、一致性&#xff08;Consistency&#xff09;&#xff1a;…

玄机平台应急响应—webshell查杀

1、前言 这篇文章说一下应急响应的内容&#xff0c;webshell查杀呢是应急响应的一部分。那么什么是应急响应呢&#xff0c;所谓的应急响应指的是&#xff0c;当网站突然出现异常情况或者漏洞时&#xff0c;能够马上根据实际问题进行分析&#xff0c;然后及时解决问题。 2、应…

内网安全-隧道搭建穿透上线内网穿透-nps自定义上线内网渗透-Linux上线-cs上线Linux主机

目录 内网安全-隧道搭建&穿透上线内网穿透-nps-自定义-上线NPS工具介绍搭建过程 nps原理介绍MSF上线CS上线 内网渗透-Linux上线-cs上线Linux主机1.下载插件2.导入插件模块3.配置监听器4.服务端配置5.配置C2监听器并生成木马6.执行木马 内网安全-隧道搭建&穿透上线 内网…

做抖店如何避免被同行内卷?这5点建议,可以解决这个问题

我是王路飞。 都说2024年的抖店不赚钱了&#xff0c;商家太多了&#xff0c;太内卷了&#xff0c;一点都不好做~ 那为什么依然有很多商家在坚持做呢&#xff1f;为什么依然有很多新手入局呢&#xff1f; 无非是抖店确实能带来可观的利润回报罢了。 那如何避免被同行内卷呢&…

idea中git检出失败

之前clone好好的&#xff0c;今天突然就拉取不下来了。很多时候是用户凭证的信息没更新的问题。由于window对同一个地址都存储了会话。如果是新的会话&#xff0c;必须要更新window下的凭证。 然后根据你的仓库找到你对应的账户&#xff0c;更新信息即可。

【前端】从手动部署到自动部署:前端项目进化之路

从手动部署到自动部署&#xff1a;前端项目进化之路 在前端开发的领域内&#xff0c;部署是一个不可忽视的环节。随着项目复杂度的增加和线上更新频率的提升&#xff0c;手动部署逐渐暴露出它的弊端。本文将带你从手动部署过渡到自动部署&#xff0c;完成前端项目进化的重要一…

Round-Robin 调度逻辑算法

Round-Robin 调度逻辑算法 1 Intro1.1 固定优先级1.2 Round-Robin算法 之前上学还是工作&#xff0c;都接触过调度算法&#xff1a;Round-Robin和weight-Round Robin算法&#xff0c;但只知道它的功能和目的是什么&#xff0c;没有具体了解如何实现的&#xff1b; 现在是工作上…

移动云服务器选购指南(图文教程详解)

目录 一、前言 二、基本概念 2.1 定义 2.2 部署形式 2.3 用处 三、主流平台 四、主流产品推荐 4.1 云电脑 4.2 云主机ECS 4.3 弹性公网 IP 五、选购指南 5.1 明确场景 5.2 明确需求 5.3 明确身份 新用户 老用户 5.4 明确时间 5.5 明确教程 六、总结 一、前言…

【多态】(超级详细!)

【多态】&#xff08;超级详细&#xff01;&#xff09; 前言一、 多态的概念二、重写1. 方法重写的规则2. 重写和重载的区别 三、多态实现的条件四、 向上转型五、动态绑定 前言 面向对象的三大特征&#xff1a;封装性、继承性、多态性。 extends继承或者implements实现&…

短视频商城全套源码:开启电商新纪元

随着数字媒体的快速发展&#xff0c;短视频平台已经成为人们获取信息、娱乐和社交的重要渠道。在这样一个大背景下&#xff0c;短视频商城的兴起&#xff0c;无疑为电商行业带来了新的机遇和挑战。本文将探讨短视频商城全套源码的重要性&#xff0c;以及它如何助力商家和开发者…

详解Spring MVC

目录 1.什么是Spring Web MVC MVC定义 2.学习Spring MVC 建立连接 RequestMapping 注解介绍及使用 获取单个参数 获取多个参数 获取普通对象 获取JSON对象 获取基础URL参数 获取上传文件 获取Header 获取Cookie 获取Session 总结 1.什么是Spring Web MVC 官⽅对于…

生成式AI模型大PK——GPT-4、Claude 2.1和Claude 3.0 Opus

RAG(检索增强生成)系统的新评估似乎每天都在发布&#xff0c;其中许多都集中在有关框架的检索阶段。然而&#xff0c;生成方面——模型如何合成和表达这些检索到的信息&#xff0c;在实践中可能具有同等甚至更大的意义。许多实际应用中的案例证明&#xff0c;系统不仅仅要求从上…

《征服数据结构》目录

我们知道要想学好算法&#xff0c;必须熟练掌握数据结构&#xff0c;数据结构常见的有 8 大类&#xff0c;分别是数组&#xff0c;链表&#xff0c;队列&#xff0c;栈&#xff0c;散列表&#xff0c;树&#xff0c;堆&#xff0c;图。但如果细分的话就比较多了&#xff0c;比如…