【发布时间】:2019-12-11 21:46:47
【问题描述】:
有没有在 Scala 中使用 Visitor Pattern 的用例?
每次在 Java 中使用访问者模式时,我是否应该在 Scala 中使用 Pattern Matching?
【问题讨论】:
-
你可以将隐式对象/方法称为访问者模式...
标签: design-patterns scala
有没有在 Scala 中使用 Visitor Pattern 的用例?
每次在 Java 中使用访问者模式时,我是否应该在 Scala 中使用 Pattern Matching?
【问题讨论】:
标签: design-patterns scala
是的,您可能应该从模式匹配开始,而不是访问者模式。看到这个interview with Martin Odersky(我的重点):
因此,适合这项工作的工具实际上取决于您的方向 想延长。如果您想使用新数据进行扩展,请选择 带有虚拟方法的经典面向对象方法。如果你想 保持数据固定并扩展新操作,然后是模式 更合适。实际上有一种设计模式——不是 与模式匹配相混淆——在面向对象编程中称为 访问者模式,它可以代表我们所做的一些事情 基于虚拟方法的面向对象的模式匹配 派遣。 但在实际使用中,访问者模式非常庞大。你 不能用模式匹配做很多很容易的事情。 您最终会遇到非常重的访客。事实证明,与 现代 VM 技术比模式匹配效率低得多。 由于这两个原因,我认为模式有明确的作用 匹配。
编辑:我认为这需要更好的解释和示例。访问者模式通常用于访问树或类似树中的每个节点,例如抽象语法树 (AST)。使用优秀的Scalariform 中的示例。 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 中的模式匹配更简洁地完成。在Scalastyle(Scala 的 Checkstyle)中,我们使用了这种方法的修改形式,但有细微的变化。我们需要遍历树,但每次检查只关心某些节点。例如,对于EqualsHashCodeChecker,它只关心定义的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 的论文 Matching Objects with Patterns 对这个问题进行了很好的调查
【讨论】:
最近(2019 年)访问者模式在 Scala 世界中引起了相当多的关注。
请参阅此处,例如 2 个有关该主题的博客:
https://meta.plasm.us/posts/2019/09/23/scala-and-the-visitor-pattern/ by 特拉维斯·布朗
https://medium.com/@supermanue/gof-design-patterns-in-scala-visitor-f3b8c91e0488 by 曼努埃尔·罗德里格斯
【讨论】: