Scala中的访客模式 [英] Visitor Pattern in Scala

查看:131
本文介绍了Scala中的访客模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Scala中是否有使用访问者模式的用例?



我应该每次在Scala中使用模式匹配我会在Java中使用访问者模式?

解决方案

是的,您应该可以从模式匹配开始,而不是访问者模式。请参阅采访Martin Odersky (我的重点):


所以这个工作的正确工具真的取决于你
想要扩展的方向。如果要使用新数据扩展,可以使用虚拟方法选择
经典面向对象的方法。如果你想要
来保持数据的固定和扩展与新的操作,那么模式
是一个更好的适合。实际上有一种设计模式 - 不要将
与模式匹配混淆 - 在面向对象编程中,
是访问者模式,它可以代表我们使用
模式匹配的一些事情面向对象的方式,基于虚拟方法
发送。 但在实际使用中,访客模式非常庞大。你
不能做很多与模式匹配很容易的事情。
结束了非常沉重的访客。而事实证明,使用
的现代虚拟机技术,它比模式匹配的方式更加不够。
由于这两个原因,我认为模式
匹配有明确的作用。


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

  private def immediateAstNodes(n:Any):List [AstNode] = n match {
case a:AstNode⇒列表(a)
case t:令牌⇒Nil
case一些(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 |假|没有|无⇒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 |假|没有|无=> List()
}

请注意,我们递归调用 visitfn (),而不是 visit()。这允许我们重复使用这种方法遍历树而不重复代码。在我们的 EqualsHashCodeChecker 中,我们有:

  private def localvisit(ast:任何):ListType = ast match {
case t:TmplDef =>列表(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 =>访问(t,localvisit)
}

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

Is there any use cases for employing the Visitor Pattern in Scala?

Should I use Pattern Matching in Scala every time I would have used the Visitor Pattern in Java?

解决方案

Yes, you should probably start off with pattern matching instead of the visitor pattern. See this interview with Martin Odersky (my emphasis):

So the right tool for the job really depends on which direction you want to extend. If you want to extend with new data, you pick the classical object-oriented approach with virtual methods. If you want to keep the data fixed and extend with new operations, then patterns are a much better fit. There's actually a design pattern—not to be confused with pattern matching—in object-oriented programming called the visitor pattern, which can represent some of the things we do with pattern matching in an object-oriented way, based on virtual method dispatch. But in practical use the visitor pattern is very bulky. You can't do many of the things that are very easy with pattern matching. You end up with very heavy visitors. And it also turns out that with modern VM technology it's way more innefficient than pattern matching. For both of these reasons, I think there's a definite role for pattern matching.

EDIT: I think this requires a bit of a better explanation, and an example. The visitor pattern is often used to visit every node in a tree or similar, for instance an Abstract Syntax Tree (AST). Using an example from the excellent Scalariform. Scalariform formats scala code by parsing Scala and then traversing the AST, writing it out. One of the provided methods takes the AST and creates a simple list of all of the tokens in order. The method used for this is:

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

This is a job which could well be done by a visitor pattern in Java, but much more concisely done by pattern matching in Scala. In Scalastyle (Checkstyle for Scala), we use a modified form of this method, but with a subtle change. We need to traverse the tree, but each check only cares about certain nodes. For instance, for the EqualsHashCodeChecker, it only cares about equals and hashCode methods defined. We use the following method:

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()
}

Notice we're recursively calling visitfn(), not visit(). This allows us to reuse this method to traverse the tree without duplicating code. In our EqualsHashCodeChecker, we have:

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)
}

So the only boilerplate here is the last line in the pattern match. In Java, the above code could well be implemented as a visitor pattern, but in Scala it makes sense to use pattern matching. Note also that the above code does not require a modification to the data structure being traversed, apart from defining unapply(), which happens automatically if you're using case classes.

这篇关于Scala中的访客模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆