Scala中的访客模式

时间:2011-12-23 16:03:13

标签: design-patterns scala

在Scala中使用Visitor Pattern是否有用例?

每次我在Java中使用访问者模式时,我是否应该在Scala中使用Pattern Matching

3 个答案:

答案 0 :(得分:45)

是的,您应该从模式匹配开始,而不是访问者模式。见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(Checkstyle for Scala)中,我们使用此方法的修改形式,但有一个微妙的变化。我们需要遍历树,但每个检查只关心某些节点。例如,对于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()之外,上述代码不需要修改所遍历的数据结构,如果您使用的是案例类,则会自动发生。

答案 1 :(得分:7)

布拉克埃米尔,马丁奥德斯基和约翰威廉姆斯在Matching Objects with Patterns的论文中对这个问题进行了很好的调查

答案 2 :(得分:2)

最近(2019年), Scala 世界中的访客模式引起了很多关注。

请参阅此处的示例2关于该主题的博客:

Travis Brown

https://meta.plasm.us/posts/2019/09/23/scala-and-the-visitor-pattern/

https://medium.com/@supermanue/gof-design-patterns-in-scala-visitor-f3b8c91e0488,由 ManuelRodríguez