gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 01:51:35 26 4
gpt4 key购买 nike

我阅读了很多关于访问者模式及其假定优势的信息。然而,在我看来,它们在实践中应用时并没有那么大的优势:

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

  • 当您真正想做的只是:
    // 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 时间方面实现上述代码片段的最快方法(如果您没有双重调度的语言或以上面伪代码的方式进行有效的类型比较)。

    问题:
  • 那么,我错过了访问者模式的哪些优势?
  • 可以使用哪些替代概念/数据结构来使上述虚构代码示例同样快速地运行?
  • 最佳答案

    就我目前所见,访问者设计模式有两个用途/好处:

  • 双派送
  • 将数据结构与其上的操作分开

  • 双派送

    假设您有一个 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);

    许多语言使用单分派(dispatch)来调用适当的函数。单分派(dispatch)意味着在运行时确定调用哪个方法时只考虑单个值。因此,我们只考虑实际的垫圈类型。不考虑 x 的实际类型。因此,最后一行代码将调用 CarWasher.Wash(Vehicle) 而不是 CarWasher.Wash(Car)。

    如果您使用一种不支持多分派(dispatch)的语言并且您确实需要它(我可以坦率地说我从未遇到过这种情况),那么您可以使用访问者设计模式来启用它。为此,需要做两件事。首先给Vehicle类(被访问者)添加一个Accept方法,它接受一个VehicleWasher作为访问者,然后调用它的操作Wash:
    Accept(VehicleWasher washer)
    washer.Wash(this);

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

    现在对于 Accept 方法的调用,将考虑 x 的实际类型(并且仅考虑 x 的类型,因为我们假设使用单一调度语言)。在 Accept 方法的实现中,Wash 方法在清洗器对象(访问者)上被调用。为此,考虑了垫圈的实际类型,这将调用 CarWasher.Wash(Car)。通过组合两个单分派(dispatch),实现了双分派(dispatch)。

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

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

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

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

    另一种方法是使用支持多分派(dispatch)的语言。例如,.NET 直到 4.0 版才支持它,它引入了 dynamic 关键字。然后在 C# 中,您可以执行以下操作:
    washer.Wash((dynamic)x);

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

    独立的数据结构和操作

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

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

    然后,您可以实现在这种情况下将具有以下方法的访问者:
    Visit(Item)

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

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

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

    关于language-agnostic - 访客模式的实际优势是什么?有哪些替代方案?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20219122/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com