访问者模式和双重分派

3

我知道这是一个经常讨论的话题,但我有一个具体的问题......我保证。

在很少接触静态类型、面向对象的世界后,我最近在阅读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方法,并相信传递的访问者会执行您想要的操作,无论那是什么。

我理解得对吗?


3
它解决了双重派发问题,但并非通过“成为”双重派发来实现。它仍然依赖于所有类型在编译时存在。虽然非常方便。 - user207421
@BasilBourque 我认为这并不相关。问题更多地涉及概念上的模式,而不是在 Crafting Interpreters 中具体使用它的方式。话虽如此,我会添加链接给那些好奇的人.. 这是一个很棒的资源。 - Solaxun
如果您的定义与此相符,我建议链接到维基百科上的“双重分派”页面。如果不一致,请考虑解释。 - Basil Bourque
@BasilBourque 嗯,我的意思是,即使是维基的第一句话也证明了我的观点:“根据调用中涉及的两个对象的运行时类型将函数调用分派到不同的具体函数。”访问者模式只在一个运行时类型上进行分派(实现accept的类型),而不是两个,对吧?第二个是静态分派并选择要调用的方法。对我来说,这真的是单分派...只是可扩展的单分派。 - Solaxun
1个回答

1
@user207421的评论非常正确。如果一种语言不支持双重分派,任何设计模式都不能改变语言使其支持。设计模式只是提供了一种替代方案,可能解决其他语言中应用双重分派时遇到的一些问题。
已经理解双重分派的人学习访问者模式时,可以通过类似这样的解释获得帮助:“访问者模式解决了与双重分派解决的一组类似的问题”。不幸的是,这个解释通常被简化为“访问者实现双重分派”,这是不正确的。
你已经认识到这一点,说明你已经很好地理解了这两个概念。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接