访问者特别有用的地方是访问者需要打开访问者类型,并且无论出于何种原因,您都不希望将该知识编码到访问者中(考虑插件架构)。考虑以下 Python 代码:
访客风采
class Banana(object):
def visit(self, visitor):
visitor.process_banana(self)
class Apple(object):
def visit(self, visitor):
visitor.process_apple(self)
class VisitorExample(object):
def process_banana(self, banana):
print "Mashing banana: ", banana
def process_banana(self, apple):
print "Crunching apple: ", apple
(请注意,我们可以使用基类/混合来压缩访问者逻辑)。
与之比较:
非访客风格
class NonVisitorVisitor(object):
def process(self, fruit):
verb = {Banana: "Mashing banana: ",
Apple: "Crunching apple: "}[type(fruit)]
print verb, fruit
在第二个示例中,水果不需要“访问者”的任何特殊支持,并且“访问者”处理给定类型的逻辑缺失。
相比之下,在 Java 或 C++ 中,第二个示例实际上是不可能的,并且访问方法(在访问者中)可以使用一个名称来引用过程方法的所有版本;编译器将选择适用于正在传递的类型的版本;并且访问者可以轻松地为访问者类型的根类提供默认实现。访问者中还需要有一个访问方法,因为方法变体(例如process(Banana b)
vs process(Apple a)
) 在编译时为访问者生成的代码中选择visit
method.
因此,在像 Python 或 Ruby 这样的语言中,没有参数类型的分派(或者更确切地说,程序员必须自己实现它),因此不需要访问者模式。或者,有人可能会说,如果不通过访问者方法进行调度,访问者模式可以更好地实现。
一般来说,在Python、Ruby或Smalltalk等动态语言中,最好让“visitee”类携带所需的所有信息(这里是动词applicable),并在必要时提供钩子来支持“visitor”,例如作为命令或策略模式,或使用此处显示的非访问者模式。
结论
非访问者是实现类型切换逻辑的一种干净方式,尽管显式类型切换通常是一种代码味道。请记住,Java 和 C++ 的实现方式也是在 Visitor 中显式切换;这些语言中模式的优雅之处在于,它避免了访问者中具有显式的切换逻辑,而这在具有无类型变量的动态语言中是不可能的。因此,顶部的访问者模式对于动态语言来说是不利的,因为它再现了静态语言中访问者模式试图避免的错误。
使用模式的问题在于,您必须了解它们想要实现的目标,以及它们如何通过具体考虑的语言机制来实现这些目标,而不是盲目地复制 UML 图。在这种情况下,实现相同优点的模式看起来不同,并且具有不同的调用模式。这样做不仅可以使它们适应不同的语言,而且还可以适应同一语言中的不同具体情况。
更新:这是一篇关于实现此模式的 ruby 文章:http://blog.rubybestpractices.com/posts/aaronp/001_double_dispatch_dance.html http://blog.rubybestpractices.com/posts/aaronp/001_double_dispatch_dance.html
对我来说,双重调度似乎相当被迫;据我所知,你可以直接取消它。