gpt4 book ai didi

design-patterns - 访问者模式中的 accept() 方法有什么意义?

转载 作者:行者123 更新时间:2023-12-03 05:15:13 25 4
gpt4 key购买 nike

有很多关于将算法与类解耦的讨论。但是,有一件事没有解释。
他们像这样使用访问者

abstract class Expr {
public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); }
}

class ExprVisitor extends Visitor{
public Integer visit(Num num) {
return num.value;
}

public Integer visit(Sum sum) {
return sum.getLeft().accept(this) + sum.getRight().accept(this);
}

public Integer visit(Prod prod) {
return prod.getLeft().accept(this) * prod.getRight().accept(this);
}
Visitor 不是直接调用visit(element),而是要求元素调用它的visit 方法。它与类(Class)不了解访客的公开想法相矛盾。
PS1 请用你自己的话解释或指出确切的解释。因为我得到的两个回答涉及一般性和不确定性。
PS2 我的猜测:自 getLeft()返回基本 Expression , 拨打 visit(getLeft())将导致 visit(Expression) , 而 getLeft()调用 visit(this)将导致另一个更合适的访问调用。所以, accept()执行类型转换(又名强制转换)。
PS3 Scala's Pattern Matching = Visitor Pattern on Steroid展示了访问者模式在没有接受方法的情况下有多简单。 Wikipedia adds to this statement :通过链接一篇论文,该论文显示“当反射可用时, accept() 方法是不必要的;为该技术引入术语‘Walkabout’。”

最佳答案

访客模式的visit/accept由于类 C 语言(C#、Java 等)的语义,构造是必要的邪恶。访问者模式的目标是使用双重调度来路由您的调用,正如您在阅读代码时所期望的那样。

通常,当使用访问者模式时,会涉及一个对象层次结构,其中所有节点都来自基础 Node。类型,以下简称 Node .本能地,我们会这样写:

Node root = GetTreeRoot();
new MyVisitor().visit(root);

问题就在这里。如果我们的 MyVisitor类定义如下:
class MyVisitor implements IVisitor {
void visit(CarNode node);
void visit(TrainNode node);
void visit(PlaneNode node);
void visit(Node node);
}

如果,在运行时,不管 root 的实际类型如何也就是说,我们的调用将进入重载 visit(Node node) .这对于声明为 Node 类型的所有变量都是正确的。 .这是为什么?因为 Java 和其他类似 C 的语言在决定调用哪个重载时只考虑参数的静态类型或声明变量的类型。 Java 没有采取额外的步骤来询问,对于每个方法调用,在运行时,“好吧, root 的动态类型是什么?哦,我明白了。它是一个 TrainNode。让我们看看其中是否有任何方法 MyVisitor 接受类型为 TrainNode ...”的参数。编译器在编译时确定要调用的方法。 (如果 Java 确实检查了参数的动态类型,性能会非常糟糕。)

Java 确实为我们提供了一种工具,用于在调用方法时考虑对象的运行时(即动态)类型 -- virtual method dispatch .当我们调用一个虚方法时,调用实际上转到了 table在由函数指针组成的内存中。每种类型都有一个表。如果某个特定方法被类覆盖,则该类的函数表条目将包含被覆盖函数的地址。如果该类没有覆盖一个方法,它将包含一个指向基类实现的指针。这仍然会导致性能开销(每个方法调用基本上都会取消引用两个指针:一个指向类型的函数表,另一个指向函数本身),但它仍然比检查参数类型要快。

访问者模式的目标是完成 double-dispatch -- 不仅要考虑调用目标的类型( MyVisitor ,通过虚方法),还要考虑参数的类型(我们在看什么类型的 Node)?访问者模式允许我们通过 visit 来做到这一点。/ accept组合。

通过将我们的行更改为:
root.accept(new MyVisitor());

我们可以得到我们想要的:通过虚方法分派(dispatch),我们输入子类实现的正确的accept()调用——在我们的例子中是 TrainElement ,我们将输入 TrainElement的实现 accept() :
class TrainNode extends Node implements IVisitable {
void accept(IVisitor v) {
v.visit(this);
}
}

编译器此时知道什么,在 TrainNode 的范围内的 accept ? 它知道 this 的静态类型是 TrainNode .这是编译器在我们的调用者范围内不知道的重要附加信息:在那里,它知道关于 root 的所有信息。是它是 Node .现在编译器知道 this ( root ) 不仅仅是 Node ,但它实际上是一个 TrainNode .结果,在 accept() 中找到了一行: v.visit(this) , 完全意味着别的东西。编译器现在将查找 visit() 的重载这需要一个 TrainNode .如果找不到,它就会将调用编译为一个重载,该重载带有 Node .如果两者都不存在,你会得到一个编译错误(除非你有一个需要 object 的重载)。因此,执行将进入我们一直以来的意图: MyVisitor的实现 visit(TrainNode e) .不需要类型转换,最重要的是,不需要反射(reflection)。因此,这种机制的开销相当低:它只包含指针引用而没有其他内容。

你的问题是对的——我们可以使用类型转换并获得正确的行为。然而,很多时候,我们甚至不知道 Node 是什么类型。以以下层次结构为例:
abstract class Node { ... }
abstract class BinaryNode extends Node { Node left, right; }
abstract class AdditionNode extends BinaryNode { }
abstract class MultiplicationNode extends BinaryNode { }
abstract class LiteralNode { int value; }

我们正在编写一个简单的编译器,它解析源文件并生成符合上述规范的对象层次结构。如果我们为作为访问者实现的层次结构编写解释器:
class Interpreter implements IVisitor<int> {
int visit(AdditionNode n) {
int left = n.left.accept(this);
int right = n.right.accept(this);
return left + right;
}
int visit(MultiplicationNode n) {
int left = n.left.accept(this);
int right = n.right.accept(this);
return left * right;
}
int visit(LiteralNode n) {
return n.value;
}
}

类型转换不会让我们走得很远,因为我们不知道 left 的类型或 rightvisit()方法。我们的解析器很可能也只返回一个 Node 类型的对象。它也指向层次结构的根,所以我们也不能安全地进行转换。所以我们的简单解释器看起来像:
Node program = parse(args[0]);
int result = program.accept(new Interpreter());
System.out.println("Output: " + result);

访问者模式让我们可以做一些非常强大的事情:给定一个对象层次结构,它允许我们创建在层次结构上操作的模块化操作,而无需将代码放在层次结构的类本身中。访问者模式被广泛使用,例如,在编译器构造中。给定特定程序的语法树,编写了许多对该树进行操作的访问者:类型检查、优化、机器代码发射通常都作为不同的访问者实现。在优化访问者的情况下,它甚至可以输出给定输入树的新语法树。

当然,它有它的缺点:如果我们在层次结构中添加一个新类型,我们还需要添加一个 visit()该新类型的方法进入 IVisitor界面,并在我们所有的访问者中创建 stub (或完整)实现。我们还需要添加 accept()方法也是如此,原因如上所述。如果性能对您来说意义不大,那么有一些解决方案可以在不需要 accept() 的情况下编写访问者。 ,但它们通常涉及反射,因此会产生相当大的开销。

关于design-patterns - 访问者模式中的 accept() 方法有什么意义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9132178/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com