¿Cuándo debo utilizar el patrón de diseño de visitantes? [cerrado]

Resuelto George Mauer asked hace 15 años • 0 respuestas

Sigo viendo referencias al patrón de visitantes en los blogs, pero debo admitir que no lo entiendo. Leí el artículo de Wikipedia sobre el patrón y entiendo su mecánica, pero todavía no sé cuándo usarlo.

Como alguien que recientemente entendió el patrón decorador y ahora ve usos para él en absolutamente todas partes, me gustaría poder entender realmente intuitivamente este patrón aparentemente útil también.

George Mauer avatar Nov 01 '08 06:11 George Mauer
Aceptado

No estoy muy familiarizado con el patrón Visitante. A ver si lo hice bien. Supongamos que tienes una jerarquía de animales.

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Supongamos que es una jerarquía compleja con una interfaz bien establecida).

Ahora queremos agregar una nueva operación a la jerarquía, es decir, queremos que cada animal emita su sonido. En la medida en que la jerarquía sea así de simple, puedes hacerlo con polimorfismo directo:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

Pero procediendo de esta manera, cada vez que quieras agregar una operación debes modificar la interfaz para cada clase de la jerarquía. Ahora supongamos que está satisfecho con la interfaz original y que desea realizarle la menor cantidad de modificaciones posibles.

El patrón Visitante le permite mover cada nueva operación a una clase adecuada y necesita extender la interfaz de la jerarquía solo una vez. Vamos a hacerlo. Primero, definimos una operación abstracta (la clase "Visitor" en GoF ) que tiene un método para cada clase en la jerarquía:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Luego, modificamos la jerarquía para aceptar nuevas operaciones:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Finalmente implementamos la operación real, sin modificar ni Cat ni Dog :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Ahora tienes una manera de agregar operaciones sin modificar más la jerarquía. Así es como funciona:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}
Federico A. Ramponi avatar Nov 01 '2008 00:11 Federico A. Ramponi

La razón de su confusión es probablemente que el nombre de Visitante es fatalmente inapropiado. Muchos (¡destacados 1 !) programadores se han topado con este problema. Lo que realmente hace es implementar el envío doble en idiomas que no lo admiten de forma nativa (la mayoría de ellos no lo hacen).


1) Mi ejemplo favorito es Scott Meyers, aclamado autor de “Effective C++”, quien llamó a este uno de sus C++ más importantes, ¡ajá! momentos de siempre .

Konrad Rudolph avatar Oct 31 '2008 23:10 Konrad Rudolph

Todos aquí tienen razón, pero creo que no aborda el "cuándo". Primero, de Patrones de diseño:

Visitor le permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.

Ahora, pensemos en una jerarquía de clases simple. Tengo las clases 1, 2, 3 y 4 y los métodos A, B, C y D. Dispóngalos como en una hoja de cálculo: las clases son líneas y los métodos son columnas.

Ahora, el diseño orientado a objetos supone que es más probable que crezcan nuevas clases que nuevos métodos, por lo que agregar más líneas, por así decirlo, es más fácil. Simplemente agrega una nueva clase, especifica qué es diferente en esa clase y hereda el resto.

A veces, sin embargo, las clases son relativamente estáticas, pero es necesario agregar más métodos con frecuencia, agregando columnas. La forma estándar en un diseño OO sería agregar dichos métodos a todas las clases, lo que puede resultar costoso. El patrón Visitante lo hace fácil.

Por cierto, este es el problema que las coincidencias de patrones de Scala pretenden resolver.

Daniel C. Sobral avatar Jan 26 '2009 02:01 Daniel C. Sobral

El patrón de diseño Visitor funciona muy bien para estructuras "recursivas" como árboles de directorios, estructuras XML o esquemas de documentos.

Un objeto Visitante visita cada nodo en la estructura recursiva: cada directorio, cada etiqueta XML, lo que sea. El objeto Visitante no recorre la estructura. En cambio, los métodos de visitante se aplican a cada nodo de la estructura.

A continuación se muestra una estructura de nodo recursivo típica. Podría ser un directorio o una etiqueta XML. [Si eres una persona de Java, imagina muchos métodos adicionales para crear y mantener la lista secundaria.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

El visitmétodo aplica un objeto Visitante a cada nodo de la estructura. En este caso, se trata de un visitante de arriba hacia abajo. Puede cambiar la estructura del visitmétodo para realizar un orden ascendente o de otro tipo.

Aquí hay una superclase para los visitantes. Es utilizado por el visitmétodo. "Llega a" cada nodo de la estructura. Dado que el visitmétodo llama upy down, el visitante puede realizar un seguimiento de la profundidad.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

Una subclase podría hacer cosas como contar nodos en cada nivel y acumular una lista de nodos, generando una buena ruta con números de sección jerárquicos.

Aquí tienes una aplicación. Construye una estructura de árbol someTree. Crea un Visitor, dumpNodes.

Luego aplica el dumpNodesal árbol. El dumpNodeobjeto "visitará" cada nodo del árbol.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

El visitalgoritmo TreeNode asegurará que cada TreeNode se utilice como argumento para el arrivedAtmétodo del Visitante.

S.Lott avatar Nov 01 '2008 02:11 S.Lott