Mai*_*kov 25 design-patterns scala
在Scala中使用访问者模式是否有任何用例?
每次我在Java中使用访问者模式时,我是否应该在Scala中使用模式匹配?
Mat*_*ell 45
是的,您应该从模式匹配开始,而不是访问者模式.看看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
Run Code Online (Sandbox Code Playgroud)
这是一项可以通过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()
}
Run Code Online (Sandbox Code Playgroud)
请注意,我们是递归调用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)
}
Run Code Online (Sandbox Code Playgroud)
所以这里唯一的样板是模式匹配的最后一行.在Java中,上面的代码可以很好地实现为访问者模式,但在Scala中使用模式匹配是有意义的.另请注意,除了定义之外,上面的代码不需要修改所遍历的数据结构,unapply()如果您使用的是案例类,则会自动发生.