我知道这是一个经常讨论的话题,但我有一个具体的问题......我保证。
在很少接触静态类型、面向对象的世界后,我最近在阅读Crafting Interpreters时遇到了这个设计模式。虽然我理解这种模式允许在一组定义良好的现有类型(类)上扩展行为(方法),但我并不完全明白它被描述为解决双重分派问题的表现,至少没有其他一些假设。我认为它更像是在表达式问题上做出折衷,你可以用开放的方法来换取封闭的类型。
在我看过的大多数例子中,你最终得到这样的东西(无耻地从精彩的Clojure Design Patterns中窃取)
public interface Visitor {
void visit(Activity a);
void visit(Message m);
}
public class PDFVisitor implements Visitor {
@Override
public void visit(Activity a) {
PDFExporter.export(a);
}
@Override
public void visit(Message m) {
PDFExporter.export(m);
}
}
public abstract class Item {
abstract void accept(Visitor v);
}
class Message extends Item {
@Override
void accept(Visitor v) {
v.visit(this);
}
}
class Activity extends Item {
@Override
void accept(Visitor v) {
v.visit(this);
}
}
Item i = new Message();
Visitor v = new PDFVisitor();
i.accept(v);
这里有一组类型(消息和活动),它们可能是封闭的或不经常更改,还有一组我们希望对扩展开放的方法(访问者)。现在让我感到困惑的是,在大多数示例中,他们将展示如何实现其他访问者而不触及现有类,例如像这样:
public class XMLVisitor implements Visitor {
@Override
public void visit(Activity a) {
XMLExporter.export(a);
}
@Override
public void visit(Message m) {
XMLExporter.export(m);
}
}
然后进行一些手势上的暗示,称这是“双重分派”,但实际上并不是。在这里,
accept
根据 Item
的子类型进行动态分派,但在 accept
内部,visit
方法通过方法重载静态地分派给传入的访问者。因此,我们在 Item
上进行了单一分派,而在 accept
中的“第二个”静态分派实际上是选择调用该 Item
类型的行为(方法)。只有一个被分派的“类型”,而不是两个——第二个是一种行为。当我想到双重分派时,我想到的是一个根据两个参数的类型进行分派的函数。一个行为,两个类型。
export(Activity,XML)
export(Activity,PDF)
export(Message,XML)
export(Message,PDF)
对我来说,这与访问者模式略有不同,访问者模式允许将任何一组行为扩展到现有类中,但这些行为不一定都像上面四个
export
示例中那样代表同一行为-它们可以是任何东西。如果我们添加另一个访问者,它可能代表导出,但它也可能不是。从API层面上看,您只需调用accept
方法,并相信传递的访问者会执行您想要的操作,无论那是什么。
我理解得对吗?