我一直在博客中看到访客模式的参考,但我不得不承认,我就是不明白。我读了维基百科上关于这个模式的文章,我理解了它的机制,但我仍然不知道什么时候使用它。

作为一个最近才真正了解装饰器模式的人,现在看到它在任何地方都有使用,我希望能够真正直观地理解这个看似方便的模式。


当前回答

这里的每个人都是对的,但我认为它没有解决“何时”这个问题。首先,从设计模式:

Visitor允许您定义一个新的 操作而不改变类 它所作用的元素。

现在,让我们考虑一个简单的类层次结构。我有类1、2、3和4,方法A、B、C和d。把它们像电子表格一样放出来:类是行,方法是列。

现在,面向对象设计假设您更有可能增长新类而不是新方法,因此添加更多行,可以说,更容易。您只需添加一个新类,指定该类中的不同之处,并继承其余部分。

但是,有时类是相对静态的,但是您需要频繁地添加更多的方法——添加列。面向对象设计的标准方法是将这样的方法添加到所有类中,这可能成本很高。访问者模式使这变得很容易。

顺便说一下,这就是Scala模式匹配想要解决的问题。

其他回答

这里的每个人都是对的,但我认为它没有解决“何时”这个问题。首先,从设计模式:

Visitor允许您定义一个新的 操作而不改变类 它所作用的元素。

现在,让我们考虑一个简单的类层次结构。我有类1、2、3和4,方法A、B、C和d。把它们像电子表格一样放出来:类是行,方法是列。

现在,面向对象设计假设您更有可能增长新类而不是新方法,因此添加更多行,可以说,更容易。您只需添加一个新类,指定该类中的不同之处,并继承其余部分。

但是,有时类是相对静态的,但是您需要频繁地添加更多的方法——添加列。面向对象设计的标准方法是将这样的方法添加到所有类中,这可能成本很高。访问者模式使这变得很容易。

顺便说一下,这就是Scala模式匹配想要解决的问题。

一种看待它的方法是,访问者模式是一种让客户端向特定类层次结构中的所有类添加额外方法的方式。

当您有一个相当稳定的类层次结构,但您对需要对该层次结构做什么有不断变化的需求时,它是有用的。

经典的例子是编译器之类的。抽象语法树(AST)可以准确地定义编程语言的结构,但是您可能希望在AST上执行的操作将随着项目的进展而变化:代码生成器、漂亮的打印机、调试器、复杂性度量分析。

如果没有访问者模式,每次开发人员想要添加一个新特性时,他们都需要将该方法添加到基类中的每个特性中。当基类出现在单独的库中或由单独的团队生成时,这尤其困难。

(我听说访问者模式与良好的OO实践相冲突,因为它将数据的操作从数据中移开了。访问者模式在正常的OO实践失败的情况下非常有用。)

Quick description of the visitor pattern. The classes that require modification must all implement the 'accept' method. Clients call this accept method to perform some new action on that family of classes thereby extending their functionality. Clients are able to use this one accept method to perform a wide range of new actions by passing in a different visitor class for each specific action. A visitor class contains multiple overridden visit methods defining how to achieve that same specific action for every class within the family. These visit methods get passed an instance on which to work.

当你考虑使用它的时候

When you have a family of classes you know your going to have to add many new actions them all, but for some reason you are not able to alter or recompile the family of classes in the future. When you want to add a new action and have that new action entirely defined within one the visitor class rather than spread out across multiple classes. When your boss says you must produce a range of classes which must do something right now!... but nobody actually knows exactly what that something is yet.

Cay Horstmann在他的OO设计和模式书中有一个很好的例子,说明了在哪里应用Visitor。他总结了这个问题:

复合对象通常具有复杂的结构,由单个元素组成。有些元素可能也有子元素. ...元素上的操作访问它的子元素,对它们应用该操作,并将结果组合在一起. ...然而,向这样的设计中添加新的操作并不容易。

不容易的原因是,操作是在结构类本身中添加的。例如,假设你有一个文件系统:

下面是一些我们可能想用这个结构实现的操作(功能):

显示节点元素的名称(一个文件列表) 显示计算出的节点元素大小(其中目录的大小包括其所有子元素的大小) 等。

You could add functions to each class in the FileSystem to implement the operations (and people have done this in the past as it's very obvious how to do it). The problem is that whenever you add a new functionality (the "etc." line above), you might need to add more and more methods to the structure classes. At some point, after some number of operations you've added to your software, the methods in those classes don't make sense anymore in terms of the classes' functional cohesion. For example, you have a FileNode that has a method calculateFileColorForFunctionABC() in order to implement the latest visualization functionality on the file system.

The Visitor Pattern (like many design patterns) was born from the pain and suffering of developers who knew there was a better way to allow their code to change without requiring a lot of changes everywhere and also respecting good design principles (high cohesion, low coupling). It's my opinion that it's hard to understand the usefulness of a lot of patterns until you've felt that pain. Explaining the pain (like we attempt to do above with the "etc." functionalities that get added) takes up space in the explanation and is a distraction. Understanding patterns is hard for this reason.

Visitor allows us to decouple the functionalities on the data structure (e.g., FileSystemNodes) from the data structures themselves. The pattern allows the design to respect cohesion -- data structure classes are simpler (they have fewer methods) and also the functionalities are encapsulated into Visitor implementations. This is done via double-dispatching (which is the complicated part of the pattern): using accept() methods in the structure classes and visitX() methods in the Visitor (the functionality) classes:

这个结构允许我们添加新的功能,这些功能作为具体的访问者在结构上工作(不需要改变结构类)。

例如,PrintNameVisitor实现目录列表功能,PrintSizeVisitor实现具有大小的版本。我们可以想象有一天有一个以XML生成数据的“ExportXMLVisitor”,或者另一个以JSON生成数据的访问者,等等。我们甚至可以让一个访问者使用图形化语言(如DOT)显示我的目录树,然后用另一个程序进行可视化。

最后要注意的是:Visitor的双重分派的复杂性意味着它更难以理解、编码和调试。简而言之,它有很高的极客因素,违背了KISS原则。在研究人员进行的一项调查中,访问者被证明是一个有争议的模式(关于它的有用性没有达成共识)。一些实验甚至表明,它并没有使代码更容易维护。

你困惑的原因可能是来客是一个致命的用词不当。许多(杰出的)程序员都曾遇到过这个问题。它实际做的是用本身不支持它的语言(大多数语言不支持)实现双重调度。


1)我最喜欢的例子是《Effective c++》的作者Scott Meyers,他称这是他最重要的c++啊哈之一!的时刻。