您什么时候决定为您的对象使用访问者?

发布于 2024-08-31 20:10:38 字数 77 浏览 2 评论 0原文

我一直认为一个对象需要数据和消息来对其进行操作。什么时候你需要一个对象外部的方法?您会遵循什么经验法则来接待访客?这是假设您完全控制对象图。

I always thought an object needs the data and the messages to act on it. When would you want a method that is extrinsic to the object? What rule of thumb do you follow to have a visitor? This is supposing that you have full control of the object graph.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

濫情▎り 2024-09-07 20:10:38

我一直认为一个对象需要
数据和对其采取行动的消息。
你什么时候想要一个方法
物体外在的?什么规则
有访客时您会遵循拇指吗?
这是假设你有完整的
对象图的控制。

有时在一个类中定义特定对象的所有行为并不方便。例如,在 Java 中,如果您的模块需要在最初在另一个模块中定义的一堆类中实现方法 toXml ,那么它会很复杂,因为您无法在其他地方编写 toXml与原始类文件相比,这意味着您无法在不更改现有源的情况下扩展系统(在 Smalltalk 或其他语言中,您可以将不与特定文件绑定的扩展方法分组)。

更一般地说,静态类型语言中存在以下能力之间的紧张关系:(1) 向现有数据类型添加新函数,以及 (2) 添加支持相同函数的新数据类型实现——这称为 表达式问题 (维基百科页面)。

面向对象语言在第 2 点上表现出色。如果您有接口,则可以安全、轻松地添加新的实现。函数式语言在第 1 点上表现出色。它们依赖于模式匹配/临时多态性/重载,因此您可以轻松地向现有类型添加新函数。

访问者模式是支持面向对象设计中的第 1 点的一种方式:您可以轻松地以类型安全的方式使用新行为来扩展系统(如果您这样做,情况就不会是这样)与 if-else-instanceof 进行手动模式匹配,因为如果未涵盖案例,该语言永远不会警告您)。

当存在一组固定的已知类型时,通常会使用访问者,我认为这就是您所说的“对象图的完全控制”的意思。示例包括解析器中的标记、具有各种类型节点的树以及类似的情况。

总而言之,我想说你的分析是正确的:)

PS:访问者模式与复合模式配合得很好,但它们单独也很有用

I always thought an object needs the
data and the messages to act on it.
When would you want a method that is
extrinsic to the object? What rule of
thumb do you follow to have a visitor?
This is supposing that you have full
control of the object graph.

It's sometimes not convenient to have all behaviors for a particular object defined in one class. For instance in Java, if your module requires a method toXml to be implemented in a bunch of classes originally defined in another module, it's complicated because you can not write toXml somewhere else than the original class file, which mean you can not extend the system without changing existing sources (in Smalltalk or other languages, you can group method in extension which are not tied to a particular file).

More generally, there's a tension in statically typed languages between the ability to (1) add new functions to existing data types, and (2) add new data types implementations supporting the same functions -- that's called the expression problem (wikipedia page).

Object oriented languages excel at point 2. If you have an interface, you can add new implementations safely and easily. Functional languages excel at point 1. They rely on pattern matching/ad-hoc polymorphism/overloading so you can add new functions to existing types easily.

The visitor pattern is a way to support point 1 in an object-oriented design: you can easily extend the system with new behaviors in a type-safe way (which wouldn't be the case if you do kind of manual pattern matching with if-else-instanceof because the language would never warn you if a case is not covered).

Visitors are then typically used when there is a fixed set of known types, which I think is what you meant by "full control of the object graph". Examples include token in a parser, tree with various types of nodes, and similar situations.

So to conclude, I would say you were right in your analysis :)

PS: The visitor pattern works well with the composite pattern, but they are also useful individually

浪推晚风 2024-09-07 20:10:38

当对相当复杂的数据结构的所有元素应用操作时(例如,并行遍历元素,或遍历高度互连的数据结构)或实现双分派,访问者模式特别有用。如果要按顺序处理元素并且不需要双重分派,那么实现自定义 Iterable 和 Iterator 通常是更好的选择,特别是因为它更适合其他 API。

The visitor pattern is particularly useful when applying an operation to all elements of a fairly complicated data structure for which traversal is non-trivial (e.g. traversing over the elements in parallel, or traversing a highly interconnected data structure) or in implementing double-dispatch. If the elements are to be processed sequentially and if double-dispatch is not needed, then implementing a custom Iterable and Iterator is usually the better choice, especially since it fits in better with the other APIs.

夏至、离别 2024-09-07 20:10:38

有时这只是组织问题。如果您有 n 种对象(即:类)和 m 种操作(即:方法),您是否希望将 n * m 类/方法对按类或方法分组?大多数面向对象语言强烈倾向于按类对事物进行分组,但在某些情况下按操作进行组织更有意义。例如,在对象图的多阶段处理中,例如在编译器中,将每个阶段(即:操作)视为一个单元通常比考虑特定类型可能发生的所有操作更有用的节点。

访问者模式的一个常见用例不仅仅是严格组织,而是打破不需要的依赖关系。例如,通常不希望您的“数据”对象依赖于表示层,特别是当您想象可能有多个表示层时。通过使用访问者模式,表示层的详细信息存在于访问者对象中,而不是存在于数据对象的方法中。数据对象本身只知道抽象访问者接口。

Sometimes its just a matter of organization. If you have n-kinds of objects (ie: classes) with m-kinds of operations (ie: methods), do you want to have the n * m class/method pairs to be grouped by class or by method? Most OO languages strongly lean towards having things grouped by class, but there are cases where organizing by operation makes more sense. For example, in a multi-phase processing of object graphs, like in a compiler, is often more useful to think about each phase (ie: operation) as a unit rather than to think about all of operations that can happen to a particular sort of node.

A common use-case for the Visitor pattern where it's more than just strictly organizational is to break unwanted dependencies. For example, it's generally undesirable to have your "data" objects depend on your presentation layer, especially if you imagine that you may have multiple presentation layers. By using the visitor pattern, details of the presentation layer live in the visitor objects, not in methods of the data objects. The data objects themselves only know about the abstract visitor interface.

三五鸿雁 2024-09-07 20:10:38

当我发现我想将一个有状态的方法放到 Entity/DataObject/BusinessObject 上时,我经常使用它,但我真的不想将这种有状态引入到我的对象中。有状态访问者可以完成这项工作,或者从我的无状态数据对象生成有状态执行器对象的集合。当工作的处理将被外包给执行程序线程时特别有用,许多有状态访问者/工作人员可以引用同一组无状态对象。

I use it a lot when I find I want to put a method that will be stateful onto Entity/DataObject/BusinessObject but I really don't want to introduce that statefulness to my object. A stateful visitor can do the job, or generate a collection of stateful executor objects from my non-stateful data objects. Particularly useful when processing of the work is going to be farmed out to executor threads, many stateful visitor/workers can reference the same group of non-stateful objects.

最单纯的乌龟 2024-09-07 20:10:38

对我来说,使用访问者模式的唯一原因是当我需要对树/特里树等图形数据结构执行双重调度时。

For me, the only one reason to use visitor pattern is when I need to perform double dispatch on graph-like data structure like tree/trie.

墨落画卷 2024-09-07 20:10:38

当您遇到以下问题时:

<块引用>

需要对异构聚合结构中的节点对象执行许多不同且不相关的操作。您希望避免这些操作“污染”节点类。而且,您不希望在执行所需的操作之前查询每个节点的类型并将指针转换为正确的类型。

然后,您可以使用具有以下意图之一的访问者模式:

  • 表示要对对象结构的元素执行的操作。
  • 定义一个新操作,而不更改其操作的元素的类。
  • 恢复丢失类型信息的经典技术。
  • 根据两个对象的类型做正确的事情。
  • 双重调度

(来自 http://sourcemaking.com/design_patterns/visitor

When you have the following problem:

Many distinct and unrelated operations need to be performed on node objects in a heterogeneous aggregate structure. You want to avoid “polluting” the node classes with these operations. And, you don’t want to have to query the type of each node and cast the pointer to the correct type before performing the desired operation.

Then you can use a Visitor pattern with one of the following intents:

  • Represent an operation to be performed on the elements of an object structure.
  • Define a new operation without changing the classes of the elements on which it operates.
  • The classic technique for recovering lost type information.
  • Do the right thing based on the type of two objects.
  • Double dispatch

(From http://sourcemaking.com/design_patterns/visitor)

梦幻的心爱 2024-09-07 20:10:38

当您需要根据对象类型(在类层次结构中)改变行为并且可以根据对象提供的公共接口来定义该行为时,访问者模式最有用。该行为不是该对象固有的,并且不会从对象中受益或不需要对象的封装。

我发现访问者通常会自然而然地出现对象的图形/树,其中每个节点都是类层次结构的一部分。为了允许客户端遍历图/树并以统一的方式处理任何每种类型的节点,访问者模式实际上是最简单的替代方案。

例如,考虑一个 XML DOM - 节点是基类,元素、属性和其他类型的节点定义类层次结构。

想象一下需求是将 DOM 作为 JSON 输出。该行为不是 Node 固有的 - 如果是的话,我们必须向 Node 添加方法来处理客户端可能需要的所有格式(toJSON()toASN1()、toFastInfoSet() 等)我们甚至可以认为 toXML() 不属于那里,尽管这可能是为了方便而提供的,因为它将是大多数客户端都使用它,并且在概念上“更接近”DOM,因此为了方便起见,可以将 toXML 内置于 Node 中 - 尽管它不是必须的,并且可以像所有其他格式一样进行处理。

由于 Node 及其子类使其状态完全可用作方法,因此我们拥有外部所需的所有信息,以便能够将 DOM 转换为某种输出格式。我们可以使用 Visitor 接口,在 Node 上使用抽象 accept() 方法,并在每个子类中实现,而不是将输出方法放在 Node 对象上。

每个访问者方法的实现处理每个节点类型的格式化。它可以做到这一点,因为所需的所有状态都可以从每个节点类型的方法中获得。

通过使用访问者,我们打开了实现任何所需输出格式的大门,而无需为每个 Node 类增加该功能的负担。

The visitor pattern is most useful when you need behaviour to vary by object type (in a class hierarchy), and that behaviour can be defined in terms of the public interface provided by the object. The behaviour is not intrinsic to that object and doesn't benefit from or require encapsulation by the object.

I find visitors often naturally arises with graphs/trees of objects, where each node is part of a class hierarchy. To allow clients to walk the graph/tree and handle any each type of node in a uniform way, the Visitor pattern is really the simplest alternative.

For example, consider an XML DOM - a Node is the base class, with Element, Attribute and other types of Node define the class hierarchy.

Imagine the requirement is to output the DOM as JSON. The behaviour is not intrinsic to a Node - if it were, we would have to add methods to Node to handle all formats that the client might need (toJSON(), toASN1(), toFastInfoSet() etc.) We could even argue that toXML() doesn't belong there, although that might be provided for convenience since it is going to be used by most clients, and is conceptually "closer" to the DOM, so toXML could be made intrinsic to Node for convenience - although it doesn't have to be, and could be handled like all the other formats.

As Node and its subclasses make their state fully available as methods, we have all the information needed externally to be able to convert the DOM to some output format. Instead of then putting the output methods on the Node object, we can use a Visitor interface, with an abstract accept() method on Node, and implementation in each subclass.

The implementation of each visitor method handles the formatting for each node type. It can do this because all the state needed is available from the methods of each node type.

By using a visitor, we open the door to implementing any output format desired without needing to burden each Node class with that functionality.

拥有 2024-09-07 20:10:38

当您完全了解实现接口的类时,我总是建议使用访问者。通过这种方式,您将不会执行任何不那么漂亮的 instanceof 调用,并且代码变得更具可读性。此外,一旦实现了访问者,就可以在现在和将来的许多地方重复使用。

I would always recommend using a visitor when you have full knowledge of what classes that implement an interface. In this way you won't do any not-so-pretty instanceof-calls, and the code becomes a lot more readable. Also, once a visitor has been implemented can be reused in many places, present and future.

清旖 2024-09-07 20:10:38

访问者模式是双重调度问题的一个非常自然的解决方案。双重调度问题是动态调度问题的一个子集,它源于这样一个事实:方法重载是在编译时静态确定的,这与虚拟(重写)方法不同,后者是在运行时确定的。

考虑这种情况:

public class CarOperations {
  void doCollision(Car car){}
  void doCollision(Bmw car){}
}

public class Car {
  public void doVroom(){}
}      

public class Bmw extends Car {
  public void doVroom(){}
}

public static void Main() {
    Car bmw = new Bmw();

    bmw.doVroom(); //calls Bmw.doVroom() - single dispatch, works out that car is actually Bmw at runtime.

    CarOperations carops = new CarOperations();
    carops.doCollision(bmw); //calls CarOperations.doCollision(Car car) because compiler chose doCollision overload based on the declared type of bmw variable
}

下面的代码来自 我之前的回答 并翻译成 Java。该问题与上面的示例有些不同,但展示了访问者模式的本质。

//This is the car operations interface. It knows about all the different kinds of cars it supports
//and is statically typed to accept only certain ICar subclasses as parameters
public interface CarVisitor {
   void StickAccelerator(Toyota car);
   void ChargeCreditCardEveryTimeCigaretteLighterIsUsed(Bmw car);
}

//Car interface, a car specific operation is invoked by calling PerformOperation  
public interface Car {

   public string getMake();
   public void setMake(string make);

   public void performOperation(CarVisitor visitor);
}

public class Toyota implements Car {
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(CarVisitor visitor) {
     visitor.StickAccelerator(this);
   }
}

public class Bmw implements Car{
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(ICarVisitor visitor) {
     visitor.ChargeCreditCardEveryTimeCigaretteLighterIsUsed(this);
   }
}

public class Program {
  public static void Main() {
    Car car = carDealer.getCarByPlateNumber("4SHIZL");
    CarVisitor visitor = new SomeCarVisitor();
    car.performOperation(visitor);
  }
}

Visitor pattern is a very natural solution to double dispatch problems. Double dispatch problem is a subset of dynamic dispatch problems and it stems from the fact that method overloads are determined statically at compile time, unlike virtual(overriden) methods, which are determined at runtime.

Consider this scenario:

public class CarOperations {
  void doCollision(Car car){}
  void doCollision(Bmw car){}
}

public class Car {
  public void doVroom(){}
}      

public class Bmw extends Car {
  public void doVroom(){}
}

public static void Main() {
    Car bmw = new Bmw();

    bmw.doVroom(); //calls Bmw.doVroom() - single dispatch, works out that car is actually Bmw at runtime.

    CarOperations carops = new CarOperations();
    carops.doCollision(bmw); //calls CarOperations.doCollision(Car car) because compiler chose doCollision overload based on the declared type of bmw variable
}

This code below is adopted from my previous answer and translated to Java. The problem is somewhat different from the above sample, but demonstrates the essence of Visitor pattern.

//This is the car operations interface. It knows about all the different kinds of cars it supports
//and is statically typed to accept only certain ICar subclasses as parameters
public interface CarVisitor {
   void StickAccelerator(Toyota car);
   void ChargeCreditCardEveryTimeCigaretteLighterIsUsed(Bmw car);
}

//Car interface, a car specific operation is invoked by calling PerformOperation  
public interface Car {

   public string getMake();
   public void setMake(string make);

   public void performOperation(CarVisitor visitor);
}

public class Toyota implements Car {
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(CarVisitor visitor) {
     visitor.StickAccelerator(this);
   }
}

public class Bmw implements Car{
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(ICarVisitor visitor) {
     visitor.ChargeCreditCardEveryTimeCigaretteLighterIsUsed(this);
   }
}

public class Program {
  public static void Main() {
    Car car = carDealer.getCarByPlateNumber("4SHIZL");
    CarVisitor visitor = new SomeCarVisitor();
    car.performOperation(visitor);
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文