我知道这是一个很好的领域,但我有一个具体的问题......我保证。
在静态类型、面向对象的世界中花费的时间很少,我最近在阅读Crafting Interpreters时遇到了这种设计模式。虽然我理解这种模式允许在一组定义良好的现有类型(类)上实现可扩展的行为(方法) ,但我并没有完全将其描述为双重分派问题的解决方案,至少在没有一些额外假设的情况下并非如此. 我认为它更多地是对表达式问题进行权衡,您可以将封闭类型换成开放方法。
在我见过的大多数示例中,您最终都会得到这样的结果(无耻地从很棒的Clojure 设计模式中窃取)
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
方法并相信传入的访问者会做你想做的事,不管它可能是什么。
我看错了吗?