Design patterns Scala中的访问者模式

Design patterns Scala中的访问者模式,design-patterns,scala,Design Patterns,Scala,是否有在Scala中使用的用例 我应该每次在Java中使用访问者模式时都在Scala中使用吗?是的,您可能应该从模式匹配开始,而不是从访问者模式开始。见此(我的重点): 因此,工作的正确工具实际上取决于你选择的方向 想要扩展。如果要使用新数据进行扩展,请选择 采用虚拟方法的经典面向对象方法。如果你愿意 要保持数据固定并使用新操作进行扩展,请选择模式 你更合适。实际上有一种设计模式是不可能的 与面向对象编程中的模式匹配相混淆 访问者模式,它可以代表我们使用 基于虚拟方法的面向对象模式匹配 调度但在

是否有在Scala中使用的用例


我应该每次在Java中使用访问者模式时都在Scala中使用吗?

是的,您可能应该从模式匹配开始,而不是从访问者模式开始。见此(我的重点):

因此,工作的正确工具实际上取决于你选择的方向 想要扩展。如果要使用新数据进行扩展,请选择 采用虚拟方法的经典面向对象方法。如果你愿意 要保持数据固定并使用新操作进行扩展,请选择模式 你更合适。实际上有一种设计模式是不可能的 与面向对象编程中的模式匹配相混淆 访问者模式,它可以代表我们使用 基于虚拟方法的面向对象模式匹配 调度但在实际使用中,访问者模式非常庞大。你 不能做很多模式匹配非常简单的事情。 到头来你会遇到很多访客。结果也证明 现代虚拟机技术比模式匹配更高效。 出于这两个原因,我认为模式有一定的作用 匹配。


编辑:我认为这需要一个更好的解释和一个例子。访问者模式通常用于访问树或类似结构中的每个节点,例如抽象语法树(AST)。使用来自优秀团队的示例。Scalariform通过解析scala,然后遍历AST并写出它来格式化scala代码。提供的方法之一接受AST并按顺序创建所有令牌的简单列表。用于此目的的方法是:

private def immediateAstNodes(n: Any): List[AstNode] = n match {
  case a: AstNode                ⇒ List(a)
  case t: Token                  ⇒ Nil
  case Some(x)                   ⇒ immediateAstNodes(x)
  case xs @ (_ :: _)             ⇒ xs flatMap { immediateAstNodes(_) }
  case Left(x)                   ⇒ immediateAstNodes(x)
  case Right(x)                  ⇒ immediateAstNodes(x)
  case (l, r)                    ⇒ immediateAstNodes(l) ++ immediateAstNodes(r)
  case (x, y, z)                 ⇒ immediateAstNodes(x) ++ immediateAstNodes(y) ++ immediateAstNodes(z)
  case true | false | Nil | None ⇒ Nil
}

def immediateChildren: List[AstNode] = productIterator.toList flatten immediateAstNodes
这项工作可以通过Java中的访问者模式来完成,但通过Scala中的模式匹配可以更简洁地完成。在(Scala的Checkstyle)中,我们使用此方法的一种修改形式,但有一个细微的变化。我们需要遍历树,但每个检查只关心某些节点。例如,对于,它只关心定义的equals和hashCode方法。我们采用以下方法:

protected[scalariform] def visit[T](ast: Any, visitfn: (Any) => List[T]): List[T] = ast match {
  case a: AstNode                => visitfn(a.immediateChildren)
  case t: Token                  => List()
  case Some(x)                   => visitfn(x)
  case xs @ (_ :: _)             => xs flatMap { visitfn(_) }
  case Left(x)                   => visitfn(x)
  case Right(x)                  => visitfn(x)
  case (l, r)                    => visitfn(l) ::: visitfn(r)
  case (x, y, z)                 => visitfn(x) ::: visitfn(y) ::: visitfn(z)
  case true | false | Nil | None => List()
}
注意,我们递归调用的是
visitfn()
,而不是
visit()
。这允许我们重用此方法来遍历树,而无需重复代码。在我们的
EqualsHashCodeChecker
中,我们有:

private def localvisit(ast: Any): ListType = ast match {
  case t: TmplDef     => List(TmplClazz(Some(t.name.getText), Some(t.name.startIndex), localvisit(t.templateBodyOption)))
  case t: FunDefOrDcl => List(FunDefOrDclClazz(method(t), Some(t.nameToken.startIndex), localvisit(t.localDef)))
  case t: Any         => visit(t, localvisit)
}

所以这里唯一的样板是模式匹配的最后一行。在Java中,上述代码可以很好地实现为访问者模式,但在Scala中,使用模式匹配是有意义的。还要注意的是,除了定义
unapply()
,上面的代码不需要修改正在遍历的数据结构,如果您使用案例类,这会自动发生。

Burak Emir、Martin Odersky和John Williams最近(2019年)在论文中对该问题进行了很好的调查访客模式在Scala世界中得到了相当多的关注

参见此处示例2:主题博客:

特拉维斯·布朗


曼努埃尔·罗德里格斯(Manuel Rodríguez)

你可以把它的对象/方法称为访问者模式——也许……不错。这是我在Scala中找到的访客模式的最佳示例代码,其访问功能与特定的“操作”代码分离。“访客模式通常用于访问树或类似结构中的每个节点”——这是一个常见的误解。访问者模式与访问几个节点无关,尽管它的名称不同。GoF的初衷是在编译器要求您精确处理所有声明的子类型时,具有严格的ADT语义。它指定封闭的子类型集,不允许添加非官方的子类型。当您有数百个节点类型,并且需要使用不同的代码访问每个节点类型时,会发生什么情况?使用递归方法是否存在堆栈溢出的风险?