design-patterns - source - visitor pattern double dispatch
PatrĂ³n de visitante en Scala (2)
¿Hay casos de uso para emplear el patrón de visitante en Scala?
¿Debería usar Pattern Matching en Scala cada vez que usaría el patrón de visitante en Java?
Hay una buena encuesta de esa pregunta en el artículo Uniendo objetos con patrones de Burak Emir, Martin Odersky y John Williams
Sí, probablemente debería comenzar con la coincidencia de patrones en lugar del patrón de visitante. Vea esta entrevista con Martin Odersky (mi énfasis):
Por lo tanto, la herramienta adecuada para el trabajo realmente depende de la dirección que desee extender. Si desea ampliar con nuevos datos, elija el enfoque clásico orientado a objetos con métodos virtuales. Si desea mantener los datos fijos y ampliarlos con nuevas operaciones, entonces los patrones se ajustan mucho mejor. En realidad, hay un patrón de diseño, que no debe confundirse con la coincidencia de patrones, en la programación orientada a objetos llamada patrón visitante, que puede representar algunas de las cosas que hacemos con la coincidencia de patrones de forma orientada a objetos, basada en el envío de métodos virtuales. Pero en el uso práctico el patrón de visitante es muy voluminoso. No se pueden hacer muchas de las cosas que son muy fáciles con la comparación de patrones. Terminas con visitantes muy pesados. Y también resulta que, con la tecnología VM moderna, es mucho más insuficiente que la comparación de patrones. Por ambas razones, creo que hay un papel definido para la comparación de patrones.
EDIT: Creo que esto requiere un poco de una mejor explicación, y un ejemplo. El patrón de visitante a menudo se usa para visitar cada nodo en un árbol o similar, por ejemplo, un árbol de sintaxis abstracta (AST). Usando un ejemplo de la excelente Scalariform . Escalariforme formatea el código de Scala analizando Scala y luego atravesando el AST, escribiéndolo. Uno de los métodos proporcionados toma el AST y crea una lista simple de todos los tokens en orden. El método utilizado para esto es:
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
Este es un trabajo que bien podría ser realizado por un patrón de visitante en Java, pero mucho más conciso por la coincidencia de patrones en Scala. En Scalastyle (Checkstyle for Scala), usamos una forma modificada de este método, pero con un cambio sutil. Necesitamos atravesar el árbol, pero cada comprobación solo se preocupa por ciertos nodos. Por ejemplo, para EqualsHashCodeChecker , solo se preocupa por los métodos equals y hashCode definidos. Utilizamos el siguiente método:
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()
}
Note que estamos llamando recursivamente visitfn()
, no visit()
. Esto nos permite reutilizar este método para atravesar el árbol sin duplicar el código. En nuestro EqualsHashCodeChecker
, tenemos:
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)
}
Así que la única repetición aquí es la última línea en la coincidencia de patrón. En Java, el código anterior podría implementarse como un patrón de visitante, pero en Scala tiene sentido utilizar la coincidencia de patrones. Tenga en cuenta también que el código anterior no requiere una modificación de la estructura de datos que se está recorriendo, además de definir no unapply()
, que ocurre automáticamente si está utilizando clases de casos.