访客模式和双重派遣
Visitor Pattern and Double Dispatch
我知道这是一个常被践踏的领域,但我有一个具体问题...我保证。
在静态类型、面向对象的世界中花费的时间很少,我最近在阅读 Crafting Interpreters. While I understand this pattern allows for extensible behavior (methods) on a set of well defined existing types (classes), I don't quite get the characterization of it as a solution to the double dispatch problem, at least not without some additional assumptions. I see it more as making a tradeoff to the expression problem 时遇到了这种设计模式,您可以在其中将封闭类型换成开放方法。
在我见过的大多数例子中,你最终会得到这样的东西(无耻地从很棒的 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);
这里我们有一组类型(Message 和 Activity),它们可能是关闭的或不经常更改的,以及一组我们希望对扩展开放的方法(访问者)。现在我感到困惑的是,在大多数示例中,它们将展示如何在不触及现有 类 的情况下实现其他访问者,例如像这样:
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)
对我来说,这与访问者模式略有不同,访问者模式允许将任何一组行为扩展到现有 类,但这些行为不一定都代表四个 [=] 中的相同行为20=] 上面的例子——它们可以是任何东西。如果我们添加另一个访客,它可能代表导出,但也可能不导出。从 API 层,您只是调用 accept
方法并相信传入的访问者会执行您想要的操作,无论是什么。
我是不是看错了?
来自@user207421 的评论是正确的。如果一种语言本身不支持双重分派,则没有任何设计模式可以改变该语言以使其支持。一种模式仅仅提供了一种替代方案,它可以解决双重分派在另一种语言中应用的一些问题。
学习访问者模式并且已经了解双重调度的人可能会得到解释的帮助,例如“访问者解决了与双重调度所解决的问题类似的一组问题”。不幸的是,这种解释通常被简化为“访问者实现双重调度”,这是不正确的。
您已经认识到这一点意味着您已经对这两个概念有了扎实的理解。
我知道这是一个常被践踏的领域,但我有一个具体问题...我保证。
在静态类型、面向对象的世界中花费的时间很少,我最近在阅读 Crafting Interpreters. While I understand this pattern allows for extensible behavior (methods) on a set of well defined existing types (classes), I don't quite get the characterization of it as a solution to the double dispatch problem, at least not without some additional assumptions. I see it more as making a tradeoff to the expression problem 时遇到了这种设计模式,您可以在其中将封闭类型换成开放方法。
在我见过的大多数例子中,你最终会得到这样的东西(无耻地从很棒的 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);
这里我们有一组类型(Message 和 Activity),它们可能是关闭的或不经常更改的,以及一组我们希望对扩展开放的方法(访问者)。现在我感到困惑的是,在大多数示例中,它们将展示如何在不触及现有 类 的情况下实现其他访问者,例如像这样:
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)
对我来说,这与访问者模式略有不同,访问者模式允许将任何一组行为扩展到现有 类,但这些行为不一定都代表四个 [=] 中的相同行为20=] 上面的例子——它们可以是任何东西。如果我们添加另一个访客,它可能代表导出,但也可能不导出。从 API 层,您只是调用 accept
方法并相信传入的访问者会执行您想要的操作,无论是什么。
我是不是看错了?
来自@user207421 的评论是正确的。如果一种语言本身不支持双重分派,则没有任何设计模式可以改变该语言以使其支持。一种模式仅仅提供了一种替代方案,它可以解决双重分派在另一种语言中应用的一些问题。
学习访问者模式并且已经了解双重调度的人可能会得到解释的帮助,例如“访问者解决了与双重调度所解决的问题类似的一组问题”。不幸的是,这种解释通常被简化为“访问者实现双重调度”,这是不正确的。
您已经认识到这一点意味着您已经对这两个概念有了扎实的理解。