2010-09-10 12 views
5

Antes de decirme que ya hay una pregunta similar, sí, lo sé, he leído it. Pero la pregunta allí se centra en cuando, estoy interesado en por qué.¿Una buena razón para usar el patrón de diseño de visitante?

Entiendo cómo funcionan las cosas. El clásico animal, perro, gato ejemplo siempre funciona como un amuleto.

La cosa es este código

int main() 
{ 
    Cat c; 
    Sound theSound; 
    c.letsDo(&theSound); 
} 

parece tan natural para mí. ¿Por qué?

Quiero decir, sí, así que tengo mis modelos de Perros y Gatos indiferenciado (primera vez que utilice esa palabra en Inglés por cierto) porque la implementació real está oculto bajo la clase de sonido, pero no es tan sólo una forma de poner a prueba tu código? ¿No es suficiente el polimorfismo para hacer algo como esto?

Para mí la diferencia es que con el polimorfismo tienes que editar cada clase (pero el modelo sigue siendo el mismo, ¿no?) Mientras que solo tienes que editar una clase con el patrón de diseño del visitante.

Respuesta

8

El patrón de visitante le permite hacer algo, lo que simplemente depende de polimorfismo no funciona con casos de uso imprevistos. Si está escribiendo una biblioteca, este es un punto importante. Permítanme elaborar:

Considere un ejemplo clásico para el uso del patrón de visitante, es decir, la operación en los nodos de algún árbol abstract syntax. Para agregar algunos detalles, digamos que acaba de escribir una biblioteca de analizador para SQL, que toma cadenas, las analiza y devuelve un AST para las cosas que encontró en la entrada. A menos que pueda anticipar todos los posibles casos de uso que su código de cliente pueda tener para tal AST, debe proporcionar una manera "genérica" ​​de recorrer el AST. Proporcionar funciones de acceso tipo DOM (getNodeType, getParentNode, getPreviousNode) es una forma. El problema aquí es que esto impone una pesada carga a los clientes de su biblioteca, porque ellos mismos tienen que hacer el despacho. Aún más, lo que necesitan saber con gran detalle, que los punteros a seguir para cada posible tipo de nodo:

void 
walk_tree(AstNode* node) 
{ 
    switch(node->getNodeType()) { 
    case SELECT_NODE: 
     for(AstNode* child = node->getFirstChild(); child; child = child->getNextNode()) { 
      walk_tree(child); 
     } 
     break; 
    ... 
    } 
} 

El patrón de visitante esta carga se mueve desde el cliente a la biblioteca.

+0

Exactamente, el comportamiento puede ser implementado por un tercero. –

+0

Ok, pero si defines el analizador para SQL, ¿no tienes ya tu definición de gramática? ¿Dónde están los casos de uso imprevistos? En realidad, ya que estamos hablando de análisis, ¿no debería ser un generador de analizador un ejemplo más apropiado? Ahí tienes una gramática arbitraria, entonces debes definir una clase de árbol genérico. – dierre

+0

No se trata de la estructura del SQL. Se trata de cómo el cliente puede usar el resultado/resultado de su biblioteca. El ejemplo del analizador simplemente se usa porque es algo "clásico". Por cierto, si tiene una API totalmente genérica (nodos arbitrarios "tipo DOM") sin una estructura fija, es posible que esté mejor con el enfoque de acceso genérico en lugar del patrón de visitante. – Dirk

3

Digamos que tiene algunas cosas básicas definidas en una biblioteca que no es suya, y necesita extenderla. Al igual que:

// In base lib: 
interface ISomething { 
    void DoSomething(); 
} 

class Something1 : ISomething { 
    // ... 
} 

class Something2 : ISomething { 
    // ... 
} 

polimorfismo permite definir las cosas nuevas se pueden realizar operaciones en:

// In your lib: 
class MySomething : ISomething { 
} 

Y ahora el lib de base puede trabajar con su MySomething como si se hubiera definido. Lo que no le permite agregar nuevas operaciones . DoSomething es lo único que podemos hacer con un ISomething. El patrón de visitante aborda eso.

El inconveniente es que con el patrón de visitante cuesta tiene la capacidad de definir nuevos tipos como acabamos de mostrar.El hecho de que la mayoría de los idiomas le permiten agregar operaciones o tipos fácilmente, pero no ambos, se llama expression problem.

El patrón de visitante es genial, pero nunca lo he encontrado fuera de la implementación de compiladores.

+0

No puede agregar nuevos tipos porque su Visitante seguirá siendo un componente de la biblioteca, ¿verdad? – dierre

+1

La clase de visitante se definirá en la biblioteca base y tendrá un conjunto fijo de métodos, uno para cada tipo. No puede agregar nuevos tipos en su lib porque no puede cambiar el visitante, y no puede mover el visitante a su lib porque los tipos en la lib base necesitan referenciarlo en sus métodos 'accept()'. – munificent

+0

lo tengo. ¡Gracias! – dierre

1

Utilicé el patrón de visitante cuando tenía un árbol de objetos y necesitaba imprimir los contenidos de muchas maneras. Comma-sep, XML, lo que sea. En lugar de agregar a la clase de árbol un nuevo método de impresión para cada formato de salida, utilicé el patrón de visitante y creé las clases CommaSepVisitor, XMLVisitor y HTMLVisitor. El código de árbol nunca cambió ya que agregué más tipos de visitantes, así que nunca introduje errores. Los visitantes mismos fueron fáciles de escribir.

2

El patrón de visitante es muy útil.

Existen al menos tres grandes razones para usarlo:

  1. reducir la proliferación de código que es sólo ligeramente diferente cuando las estructuras de datos cambian.

  2. Aplica el mismo cálculo a varias estructuras de datos, sin cambiar el código que implementa el cálculo.

  3. Agregue información a las bibliotecas heredadas sin cambiar el código heredado.

favor, eche un vistazo a an article I've written about this.

Cheers

+0

tnx, lo leeré. – dierre

Cuestiones relacionadas