访客模式的实际优势是什么?有哪些替代方案?

2024-04-18

我读了很多关于访客模式及其假定优势的内容。然而对我来说,在实践中应用它们似乎并没有那么大的优势:

  • “方便”和“优雅”似乎意味着大量的样板代码
  • 因此,代码很难遵循。另外,“接受”/“访问”的描述性并不强
  • 如果您的编程语言没有方法重载(即 Vala),那么样板代码甚至会更丑
  • 通常,您无法在不修改所有类的情况下向现有类型层次结构添加新操作,因为您需要新的“接受”/“访问”方法到处一旦您需要具有不同参数和/或返回值的操作(对整个地方的类进行更改是该设计模式应该避免的一件事!?)
  • 将新类型添加到类型层次结构需要更改all访客。此外,您的访问者不能简单地忽略类型 - 您需要创建一个空的访问方法(再次样板)

当您实际上想做的只是:

// Pseudocode
int SomeOperation(ISomeAbstractThing obj) {
    switch (type of obj) {
        case Foo: // do Foo-specific stuff here
        case Bar: // do Bar-specific stuff here
        case Baz: // do Baz-specific stuff here
        default: return 0; // do some sensible default if type unknown or if we don't care
    }
}

我看到的唯一真正的优势(顺便说一句,我在任何地方都没有提到过):访问者模式可能是在 cpu 时间方面实现上述代码片段的最快方法(如果您没有双重调度的语言或以上面伪代码的方式进行有效的类型比较)。

问题:

  • 那么,我错过了访客模式的哪些优点呢?
  • 可以使用哪些替代概念/数据结构来使上述虚构的代码示例同样快速地运行?

据我所知,访问者设计模式有两个用途/好处:

  1. 双调度
  2. 将数据结构与其上的操作分开

双调度

假设您有一个 Vehicle 类和一个 VehicleWasher 类。 VehicleWasher 有一个 Wash(Vehicle) 方法:

VehicleWasher
    Wash(Vehicle)

Vehicle

此外,我们还有特定的车辆,例如汽车,将来我们还将拥有其他特定的车辆。为此,我们有一个 Car 类,但也有一个特定的 CarWasher 类,它具有特定于洗车的操作(伪代码):

CarWasher : VehicleWasher
    Wash(Car)

Car : Vehicle

然后考虑以下客户端代码来清洗特定车辆(请注意,x 和 washer 是使用其基本类型声明的,因为实例可能是根据用户输入或外部配置值动态创建的;在本示例中,它们只是使用新运算符创建的尽管):

Vehicle x = new Car();
VehicleWasher washer = new CarWasher();

washer.Wash(x);

许多语言使用单一调度来调用适当的函数。单一调度意味着在运行时在确定调用哪个方法时仅考虑单个值。因此,我们只会考虑洗衣机的实际类型。不考虑 x 的实际类型。因此,最后一行代码将调用 CarWasher.Wash(Vehicle) 并NOT洗车机.洗(汽车)。

如果您使用的语言不支持多重分派,但您确实需要它(我可以诚实地说我从未遇到过这种情况),那么您可以使用访问者设计模式来启用此功能。为此,需要做两件事。首先,向 Vehicle 类(访问者)添加一个 Accept 方法,该方法接受 VehicleWasher 作为访问者,然后调用其操作 Wash:

Accept(VehicleWasher washer)
    washer.Wash(this);

第二件事是修改调用代码,替换washer.Wash(x);符合以下条件:

x.Accept(washer);

现在,对于 Accept 方法的调用,将考虑 x 的实际类型(并且仅考虑 x 的类型,因为我们假设使用单一调度语言)。在 Accept 方法的实现中,对 Washer 对象(访问者)调用 Wash 方法。为此,需要考虑洗衣机的实际类型,这将调用 CarWasher.Wash(Car)。通过组合两个单调度,可以实现双调度。

现在详细说明您对“接受并访问”和“访客”等术语的评论非常不具体。这是绝对正确的。但这是有原因的。

考虑此示例中的要求,实现一个能够修理车辆的新类:VehicleRepairer。仅当该类继承自 VehicleWasher 并将其修复逻辑包含在 Wash 方法内时,该类才能在本示例中用作访问者。但这当然没有任何意义,而且会令人困惑。所以我完全同意设计模式往往有非常模糊和不具体的命名,但这确实使它们适用于许多情况。您的命名越具体,限制就越多。

您的 switch 语句仅考虑一种类型,这实际上是单次调度的手动方式。以上述方式应用访问者设计模式将提供双重调度。 这样,在向层次结构添加其他类型时,您不一定需要其他 Visit 方法。当然,它确实增加了一些复杂性,因为它使代码的可读性降低。但当然,所有模式都是有代价的。

当然,这种模式并不总是可用。如果您期望使用多个参数进行大量复杂操作,那么这不是一个好的选择。

另一种方法是使用支持多重调度的语言。例如,.NET 直到 4.0 版本引入了动态关键字才支持它。然后在 C# 中你可以执行以下操作:

washer.Wash((dynamic)x);

因为 x 然后被转换为动态类型,所以将在调度时考虑其实际类型,因此 x 和 washer 将用于选择正确的方法,以便调用 CarWasher.Wash(Car) (使代码正常工作,并且保持直觉)。

分离的数据结构和操作

另一个好处和要求是它可以将数据结构与操作分开。这可能是一个优势,因为它允许添加具有自己的操作的新访问者,同时还允许添加“继承”这些操作的数据结构。然而,只有当这种分离可以完成/有意义时才可以应用。执行操作的类(访问者)不知道数据结构的结构,也不必知道使代码更易于维护和重用的因素。当出于这个原因应用时,访问者可以对数据结构中的不同元素进行操作。

假设您有不同的数据结构,它们都由 Item 类的元素组成。结构可以是列表、堆栈、树、队列等。

然后,您可以实现访问者,在本例中将具有以下方法:

Visit(Item)

数据结构需要接受访问者,然后为每个项目调用 Visit 方法。

通过这种方式,您可以实现各种访问者,并且仍然可以添加新的数据结构,只要它们包含 Item 类型的元素即可。

对于具有附加元素(例如节点)的更具体的数据结构,您可以考虑从传统访问者继承的特定访问者(NodeVisitor),并让您的新数据结构接受该访问者(Accept(NodeVisitor))。新的访问者可以用于新的数据结构,但由于继承也可以用于旧的数据结构,因此您不需要修改现有的“接口”(在本例中为超类)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

访客模式的实际优势是什么?有哪些替代方案? 的相关文章

  • 使用带有返回值的访问者模式实现 AST 的最佳方法是什么?

    我正在尝试使用访问者模式在 C 中实现一个简单的抽象语法树 AST 通常访问者模式不处理返回值 但在我的 AST 中 有一些表达式节点关心其子节点的返回类型和值 例如 我有一个这样的 Node 结构 class AstNode public
  • 优化康威的“生命游戏”

    为了进行实验 我 很久以前 实施了康威的生命游戏 http en wikipedia org wiki Conway s Game of Life 而且我知道this https stackoverflow com questions 18
  • 如何抑制自己重写一切的强烈冲动?

    Setup 您是否有过这样的经历 在一段代码中进行看似简单的更改 然后意识到您刚刚踏入了一片值得认真关注的荒地 这通常会由官方跟进吓坏了那一刻 重写眼前一切的压倒性感觉开始蔓延 值得注意的是 这些糟糕的代码不一定来自其他人 因为它可能确实是
  • (以编程方式)比较 PDF 的可靠方法? [复制]

    这个问题在这里已经有答案了 可能的重复 比较大量 PDF 文件的工具 https stackoverflow com questions 145657 tool to compare large numbers of pdf files 我
  • 如何检测(心电图)波的模式?

    我正在尝试读取心电图图像并检测其中的每个主波 P 波 QRS 波群和 T 波 我可以读取图像并获得向量 例如 4 2 4 4 4 9 4 7 我需要一种算法来遍历这个向量并检测每个波何时开始和结束 一个例子 如果它们总是具有相同的大小 或者
  • 为什么不是每种类型的对象都可序列化?

    为什么不是每种类型的对象都是隐式可序列化的 以我有限的理解 对象不就是简单地存储在堆上并将指向它们的指针存储在堆栈上吗 难道您不应该能够以编程方式遍历它们 以通用格式存储它们 并且还能够从那里重建它们吗 某些对象封装了无法访问的资源 例如文
  • 将 Eclipse 的“开放调用层次结构”过滤为仅我的公司/项目

    我最喜欢的 Eclipse 功能之一是能够打开调用者 被调用者层次结构 http eclipse tools sourceforge net call hierarchy index html的一个方法 默认情况下 该视图显示对我的代码库之
  • 将数字缩放为 <= 255?

    我的单元格的数值可以是 0 到 0 之间的任何值Integer MAX VALUE 我想对这些单元格进行相应的颜色编码 如果该值 0 则 r 0 如果该值是Integer MAX VALUE 则 r 255 但是中间的值呢 我想我需要一个函
  • 语法分析和语义分析有什么区别?

    据我了解 Parser由词法分析 句法分析和语义分析三个阶段组成 Lexical 它将我的输入分割成标记 例子 123 100 0 gt 123 100 0 语法 它将研究标记并检查它们是否彼此有意义 我遇到的问题是理解最后阶段的 语义解析
  • 如何使用 Trie 进行拼写检查

    我有一个根据单词词典构建的特里树 我想用它来进行拼写检查 并建议字典中最接近的匹配项 也许对于给定数量的编辑x 我想我会在目标单词和字典中的单词之间使用 levenshtein 距离 但是有没有一种聪明的方法可以遍历 trie 而不需要对每
  • 基本编程/算法概念[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我即将 与其他程序员一起 在我的高中
  • 有效地将相似的数字分组在一起[重复]

    这个问题在这里已经有答案了 可能的重复 一维数数组聚类 https stackoverflow com questions 11513484 1d number array clustering 我有一个数字数组 例如 1 20 300 4
  • 多维数组(如 C/C++ 中的数组)是不规则数组的特殊情况吗? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我和一个哥们讨论了C 和C多维数组是否是不规则数组的特例 一种观点是 多维数组不是参差不齐的数组 因为多维数组的每个元素具有相同的大小 在参差不齐的数
  • Lockfree 标准集合和教程或文章

    有人知道用于无锁常用数据类型的实现 即源代码 的好资源吗 我正在考虑列表 队列等 锁定实现非常容易找到 但我找不到无锁算法的示例以及 CAS 的工作原理以及如何使用它来实现这些结构 查看 Julian M Bucknall 的博客 他 详细
  • 如何为抽象工厂创建的类设置特定属性?

    是否可以让具体工厂使用抽象工厂模式为其创建具有特定类型参数的具体类 或者由各自的具体工厂创建的不同具体类是否需要具有相同的字段 例如 在下图中 您将如何使用客户端 应用程序 给出的不同参数集来实例化 WinButton 和 OSXButto
  • 生成所有多集大小为 n 的分区的算法

    我一直在试图找出一种方法来生成多重集的所有不同的大小为 n 的分区 但到目前为止却空手而归 首先让我展示一下我想要实现的目标 假设我们有一个输入向量uint32 t std vector
  • 快速约会算法

    我在一家咨询公司工作 大部分时间都在客户所在地 正因为如此 我很少见到同事 为了更好地了解彼此 我们将安排一个晚宴 会有很多小桌子 方便人们聊天 为了在聚会期间与尽可能多的不同的人交谈 每个人都必须每隔一段时间 比如每小时 换一张桌子 如何
  • “此应用程序已请求运行时以异常方式终止它”的原因是什么?

    Visual C 运行时抛出一个常见错误 此应用程序已请求运行时以异常方式终止它 请联系应用程序的支持团队以获取更多信息 该错误消息实际上是什么意思mean 让我用一个比喻来准确地解释我的问题 如果我看到一条消息 异常 访问冲突 0xc00
  • 面向对象编程语言中的引用默认情况下是否应该不可为空? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • “对象之间通过传递消息进行通信”到底是如何实现的?

    在几本有关面向对象编程的介绍性文本中 我遇到过上述陈述 来自维基百科 在 OOP 中 每个对象都能够接收消息 处理数据 以及发送消息与其他对象相关 并且可以被视为具有独特角色或责任的独立 机器 该语句在代码中到底意味着什么 class A

随机推荐

  • SAS 全球日期比较

    我正在尝试进行日期比较 但没有得到正确的结果 有谁知道发生了什么事吗 macro ttt let check start 28APR2014 if check start d lt 25may2014 d then let true 1 e
  • 如何使 multer 正确解析使用表单数据发送的客户端请求?

    我想将数据从客户端传输到双方都使用的服务器计算机Node js 在客户端我正在使用库axios and form data 在我正在使用的服务器上express and multer 我正在使用的客户端代码如下 const FormData
  • MarkerView 走出图表以获取图表上的最后一个点

    我正在使用 MarkerView 类在图表中显示标记视图 我创建的标记视图布局包含两个文本视图 一个在另一个下面 我面临的问题是图表上最后一点的标记视图一半在图表内 一半在图表外 下面两张图清楚地说明了问题 第一张图片显示了图表中心点的标记
  • 通过在 MATLAB 中填充当前值来扩展数组

    我有一个相当简单的问题 我只是想知道在 MATLAB 中是否有一种简单的方法可以做到这一点 即一个函数来完成此任务 而不是自己编写循环或其他东西 假设我有一个时间序列 其中 Time 是1 1 1000和数据是2 1 1 1000 我想通过
  • 如何在不使用依赖注入的情况下模拟另一个包中的函数?

    有点像 golang 初学者 但我以前曾使用过测试框架 如何在不注入依赖项的情况下模拟和伪造依赖方法返回的内容 我不想使用依赖项注入的原因是因为正在使用许多外部包方法 并且在构造函数中注入所有方法很麻烦 我已经在线 stackoverflo
  • phpmyadmin 中令人讨厌的警告[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 每次我登录 phpMyAdmin 时 我都会在红色框中收到一条恼人的警告消息 我已经卸载了 xampp 然后再次安装了几次 我删除
  • Google_maps_flutter 在 iOS 中不显示缩放和路线按钮

    在 IOS 中使用 google map flutter api 时 不会显示缩放和路线按钮 当点击地图上的任何位置时 这些按钮会在 Android 中显示 但是 IOS 中不存在相同的行为 我希望这些按钮在 IOS 中也可用 IOS 中仅
  • 使用MSBuild与VS2017构建SSDT项目失败

    我正在尝试在我们的 Windows Server 2016 服务器上设置 sqlproj SSDT 项目的持续集成 为此 我在服务器上安装了 VS2017 和 SSDT 工具以获得所需的工具 我还安装了 MS build tools 201
  • 嵌套形式和 habtm

    我正在尝试保存到 habtm 关系中的联接表 但遇到问题 在我看来 我传递的组 ID 为 User model user rb class User lt ActiveRecord Base has and belongs to many
  • 配置HttpClientFactory时证书错误

    我需要添加证书HttpClientFactory 旧的实现与HttpClient看看这个 var cookieContainer new CookieContainer var handler new HttpClientHandler C
  • 如何将一列中的文本拆分为多行

    我正在处理一个大型 csv 文件 倒数第二列有一个文本字符串 我想用特定分隔符分割它 我想知道是否有一种简单的方法可以使用 pandas 或 python 来做到这一点 CustNum CustomerName ItemQty Item S
  • 使用 ASP.NET 4.5 应用程序中需要 WS-Security 的 Web 服务

    我需要使用一个 Web 服务 该服务需要基于 ASP NET 4 5 应用程序的 X 509 证书的 WS Security 到目前为止 我已经创建了 Web 参考 但我不知道如何实现 WS Security 我获得证书没有问题 但我不知道
  • svn 可以提供哪些报告?

    我们正在迁移到 SVN 对我们来说不幸的是 我们会定期接受审核 审核员需要以下信息 文件更改历史记录SVN访问历史添加了新文件文件的变化 是否有一个工具可以为我们生成这些报告 或其中一些报告 StatSVN是一个轻量级的颠覆报告生成器 ht
  • 如何使用jquery获取动态创建的没有Id的子元素的高度

    我有一个 div 它总是动态加载两个图像 并且可能在中间加载一个 div 图像或 div 都没有与之关联的 id 而且我不能让它们有 id 用萤火虫检查它们 它们只是显示为 img and div 我需要获取这个子 div 存在时的高度 我
  • 打印用户输入的两个最高值

    我有一个作业 我必须编写一段代码 让用户决定要写入的 int 值的数量 然后决定这些值应该是什么 用户必须至少有 2 个输入 然后程序将比较输入的值 然后打印出两个最高值 到目前为止 我设法打印出最高的值 但我不确定我的做法有什么问题 因为
  • Chrome 更新破坏了我的滚动子菜单

    在 Windows 和 Osx 中将 Chrome 更新到版本 56 它破坏了我的滚动子菜单 如果我用鼠标滚轮滚动 滚动操作就会起作用 但是如果我将鼠标指针悬停在滚动条上 子菜单就会关闭 我使用以下 css 来滚动子菜单 ul scroll
  • 如何将所有text_node节点值的一部分包装在html元素中?

    我正在迭代 html 文档中的所有文本节点 以便用特定的范围包围一些单词 改变nodeValue不允许我插入 html 这span被转义以纯文本显示 我不希望这样 这是我到目前为止所拥有的 var elements document get
  • 我无法在react-dom/client中使用createRoot函数

    import React from react import as ReactDOMClient from react dom client import App from App import reportWebVitals from r
  • 提取授权号的正则表达式模式

    我在用着GSKinner 的 Reg Exr 工具 http gskinner com RegExr 帮助提出一种模式 可以在包含大量其他垃圾的字段中找到授权号 授权号是一个包含字母 有时 数字 始终 和连字符 有时 的字符串 i e 授权
  • 访客模式的实际优势是什么?有哪些替代方案?

    我读了很多关于访客模式及其假定优势的内容 然而对我来说 在实践中应用它们似乎并没有那么大的优势 方便 和 优雅 似乎意味着大量的样板代码 因此 代码很难遵循 另外 接受 访问 的描述性并不强 如果您的编程语言没有方法重载 即 Vala 那么