2011-12-23 11 views

Respuesta

41

Sí, probablemente debería comenzar con la coincidencia de patrones en lugar del patrón de visitante. Ver este interview with Martin Odersky (el subrayado es mío):

lo tanto la herramienta correcta para el trabajo depende realmente de la dirección que desea extender. Si desea ampliar con nuevos datos, elija el método clásico orientado a objetos con métodos virtuales. Si desea para mantener los datos corregidos y ampliar con nuevas operaciones, los patrones encajan 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 el patrón de visitante, que puede representar algunas de las cosas que hacemos con la coincidencia de patrones de forma orientada a objetos, basado en el método virtual despacho. Pero en el uso práctico, el patrón de visitante es muy voluminoso. Usted no puede hacer muchas de las cosas que son muy fáciles con la coincidencia de patrones. Terminas recibiendo muchos visitantes. Y también resulta que con la tecnología de VM moderna es mucho más eficiente que la coincidencia de patrones. Por estos dos motivos, creo que hay una función definida para el patrón correspondiente.

EDIT: Creo que esto requiere un poco de una mejor explicación, y un ejemplo. El patrón de visitante a menudo se utiliza para visitar todos los nodos de un árbol o similar, por ejemplo, un Árbol de sintaxis abstracta (AST). Usando un ejemplo del excelente Scalariform. Los formatos escalariformes escalan codificando al analizar Scala y luego recorriendo 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 ello 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 concisa hecho por la coincidencia de patrones en Scala. En Scalastyle (Checkstyle for Scala), utilizamos una forma modificada de este método, pero con un cambio sutil. Necesitamos atravesar el árbol, pero cada control solo se preocupa por ciertos nodos. Por ejemplo, para el EqualsHashCodeChecker, solo se preocupa por los métodos equals y hashCode definidos. Utilizamos el método siguiente:

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

cuenta que estamos llamando de forma recursiva visitfn(), no visit(). Esto nos permite reutilizar este método para atravesar el árbol sin duplicar el código. En nuestra 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 repetitivo aquí es la última línea de la coincidencia de patrón. En Java, el código anterior bien podría implementarse como un patrón de visitante, pero en Scala tiene sentido usar la coincidencia de patrones. Tenga en cuenta también que el código anterior no requiere una modificación en la estructura de datos que se está recorriendo, además de definir unapply(), que ocurre automáticamente si está utilizando clases de casos.

+0

Niza. Este es el mejor código de ejemplo que he podido encontrar del patrón de visitante en Scala con la funcionalidad de visita separada del código específico de "acción". – Core

+0

"El patrón de visitante a menudo se utiliza para visitar todos los nodos de un árbol o similar" - En mi opinión, este es un concepto erróneo común. El patrón del visitante no tiene nada que ver con hacer una visita a varios nodos, a pesar de su nombre. La intención original en GoF es tener una semántica ADT estricta, cuando el compilador le ordena manejar exactamente todos los subtipos declarados. Especifica un conjunto cerrado de subtipos y no permite agregar subtipos no oficiales. – beefeather

+0

¿Qué sucede cuando tienes cientos de tipos de nodos y necesitas visitar cada uno con un código diferente? –

Cuestiones relacionadas