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

315

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

Como alguien que recientemente acaba de obtener el patrón de decorador y ahora lo está usando en todas partes, me gustaría poder entender intuitivamente este patrón aparentemente útil también.

George Mauer
fuente
77
Finalmente lo obtuve después de leer este artículo de Jermey Miller en mi blackberry mientras estaba atrapado esperando en un lobby durante dos horas. Es largo, pero ofrece una maravillosa explicación del envío doble, visitante y compuesto, y de lo que puede hacer con estos.
George Mauer el
1
Aquí hay un buen artículo: codeproject.com/Articles/186185/Visitor-Design-Pattern
Seyed Morteza Mousavi
3
Patrón de visitante? ¿Cúal? El punto es: hay muchos malentendidos y pura confusión en torno a este patrón de diseño. He escrito un artículo que, con suerte, pone algo de orden en este caos: rgomes-info.blogspot.co.uk/2013/01/…
Richard Gomes
Cuando desee tener objetos de función en tipos de datos de unión, necesitará un patrón de visitante. Quizás se pregunte qué son los objetos de función y los tipos de datos de unión, entonces vale la pena leer ccs.neu.edu/home/matthias/htdc.html
Wei Qiu
Ejemplos aquí y aquí .
jaco0646

Respuestas:

315

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

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

(Suponga 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 haga su sonido. En la medida en que la jerarquía sea así de simple, puede 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 de esta manera, cada vez que desee agregar una operación, debe modificar la interfaz para cada clase de la jerarquía. Ahora, supongamos que está satisfecho con la interfaz original y que desea realizar la menor cantidad posible de modificaciones.

El patrón Visitante le permite mover cada nueva operación en 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 "Visitante" 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 tiene 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
fuente
19
S.Lott, caminar por un árbol no es en realidad el patrón de visitante. (Es el "patrón de visitante jerárquico", que es confusamente completamente diferente). No hay forma de mostrar el patrón de visitante de GoF sin usar la herencia o la implementación de la interfaz.
munificente
14
@Knownasilya: Eso no es cierto. El & -Operator proporciona la dirección del objeto de sonido, que necesita la interfaz. letsDo(Operation *v) necesita un puntero
AquilaRapax 01 de
3
solo por claridad, ¿es correcto este ejemplo de patrón de diseño de visitante?
godzilla
44
Después de pensar mucho, me pregunto por qué llamaste a dos métodos hereIsADog y hereIsACat, aunque ya pasaste el perro y el gato a los métodos. Preferiría un simple performTask (Object * obj) y emitirás este objeto en la clase Operation. (y en un lenguaje que respalda la anulación, no hay necesidad de emitir)
Abdalrahman Shatou
66
En su ejemplo "principal" al final: theSound.hereIsACat(c)habría hecho el trabajo, ¿cómo justifica todos los gastos generales introducidos por el patrón? El doble despacho es la justificación.
franssu
131

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


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

Konrad Rudolph
fuente
3
+1 "no hay patrón": la respuesta perfecta. la respuesta más votada demuestra que muchos programadores de c ++ aún no se dan cuenta de las limitaciones de las funciones virtuales sobre el polimorfismo "adhoc" utilizando un tipo enum y switch case (la forma c). Puede ser más ordenado e invisible usar virtual, pero todavía está limitado a un solo envío. En mi opinión personal, este es el mayor defecto de c ++.
user3125280
@ user3125280 He leído 4/5 artículos y el capítulo de Patrones de diseño sobre el patrón Visitante ahora, y ninguno de ellos explica la ventaja de usar este patrón oscuro sobre un caso, o cuando podría usar uno sobre el otro. ¡Gracias por al menos mencionarlo!
spinkus
44
@sam Estoy bastante seguro de que lo explican: es la misma ventaja que siempre obtienes de la subclasificación / polimorfismo de tiempo de ejecución sobre switch: switchcodifica la toma de decisiones en el lado del cliente (duplicación de código) y no ofrece verificación de tipo estático ( verificar la integridad y distinción de casos, etc.). El verificador de tipos verifica un patrón de visitante y, por lo general, simplifica el código del cliente.
Konrad Rudolph
@KonradRudolph gracias por eso. Sin embargo, teniendo en cuenta que no se aborda explícitamente en Patterns o en el artículo de Wikipedia, por ejemplo. No estoy en desacuerdo con usted, pero podría argumentar que el uso de un caso también es beneficioso, por lo que es extraño que generalmente no se contrasta: 1. no necesita un método accept () en los objetos de su colección. 2. El ~ visitante puede manejar objetos de tipo desconocido. Por lo tanto, el caso stmt parece una mejor opción para operar en estructuras de objetos con una colección variable de tipos involucrados. Los patrones admiten que el patrón Visitor no se adapta bien a tal escenario (p333).
spinkus
1
@SamPinkus konrad está en el clavo, es por eso virtualque las características son tan útiles en los lenguajes de programación modernos, son el bloque de construcción básico de los programas extensibles, en mi opinión, la forma c (interruptor anidado o coincidencia de patrón, etc., dependiendo de su idioma de elección) es mucho más limpio en el código que no necesita ser extensible y me ha sorprendido gratamente ver este estilo en un software complicado como el prover 9. Más importante aún, cualquier lenguaje que quiera proporcionar extensibilidad probablemente debería acomodar mejores patrones de envío que el envío único recursivo (es decir, visitante).
user3125280
84

Todos aquí están en lo correcto, 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 en 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 crezca 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 necesita agregar más métodos con frecuencia, agregando columnas. La forma estándar en un diseño OO sería agregar tales métodos a todas las clases, lo que puede ser costoso. El patrón de visitante hace que esto sea fácil.

Por cierto, este es el problema que el patrón de Scala intenta resolver.

Daniel C. Sobral
fuente
¿Por qué usaría el patrón de visitante solo en una clase de utilidad? Puedo llamar a mi clase de utilidad de esta manera: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Cual es la diferencia ? ambos hacen separación de la preocupación ¿verdad? Espero que puedas ayudar.
j2emanue
@ j2emanue Porque el patrón Visitor utiliza la sobrecarga correcta de Visitor en tiempo de ejecución. Si bien su código necesita conversión de tipo para llamar a la sobrecarga correcta.
Acceso denegado el
¿hay una ganancia de eficiencia con eso? supongo que evita lanzar es una buena idea
j2emanue
@ j2emanue, la idea es escribir código que se ajuste al principio abierto / cerrado, no a razones de rendimiento. Ver abierto cerrado en tío Bob butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Acceso denegado el
22

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 Visitor visita cada nodo en la estructura recursiva: cada directorio, cada etiqueta XML, lo que sea. El objeto Visitor no recorre la estructura. En cambio, los métodos de visitante se aplican a cada nodo de la estructura.

Aquí hay una estructura de nodo recursiva típica. Podría ser un directorio o una etiqueta XML. [Si eres una persona Java, imagina muchos métodos adicionales para construir y mantener la lista de niños.]

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 Visitor a cada nodo en la estructura. En este caso, es un visitante de arriba hacia abajo. Puede cambiar la estructura del visitmétodo para hacer un pedido ascendente o de otro tipo.

Aquí hay una superclase para visitantes. Se usa por el visitmétodo. "Llega" a cada nodo en la estructura. Como 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 de números de sección jerárquica.

Aquí hay una aplicación. Se construye una estructura de árbol, someTree. Se crea una Visitor, dumpNodes.

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

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

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

S.Lott
fuente
8
Como otros han dicho, este es el "patrón jerárquico de visitantes".
PPC-Coder
1
@ PPC-Coder ¿Cuál es la diferencia entre 'patrón de visitante jerárquico' y patrón de visitante?
Tim Lovell-Smith
3
El patrón de visitante jerárquico es más flexible que el patrón de visitante clásico. Por ejemplo, con el patrón jerárquico puede realizar un seguimiento de la profundidad del recorrido y decidir qué rama atravesar o dejar de atravesar todos juntos. El visitante clásico no tiene este concepto y visitará todos los nodos.
PPC-Coder
18

Una forma de verlo es que el patrón de visitante es una forma de permitir que sus clientes agreguen métodos adicionales a todas sus clases en una jerarquía de clases particular.

Es útil cuando tiene una jerarquía de clases bastante estable, pero tiene requisitos cambiantes de lo que debe hacerse con esa jerarquía.

El ejemplo clásico es para compiladores y similares. Un árbol de sintaxis abstracta (AST) puede definir con precisión la estructura del lenguaje de programación, pero las operaciones que desee realizar en el AST cambiarán a medida que avance su proyecto: generadores de códigos, impresoras bonitas, depuradores, análisis de métricas de complejidad.

Sin el Patrón de visitante, cada vez que un desarrollador quisiera agregar una nueva característica, necesitaría agregar ese método a cada característica en la clase base. Esto es particularmente difícil cuando las clases base aparecen en una biblioteca separada, o son producidas por un equipo separado.

(He oído argumentar que el patrón Visitor está en conflicto con las buenas prácticas de OO, porque aleja las operaciones de los datos de los datos. El patrón Visitor es útil precisamente en la situación en que las prácticas normales de OO fallan).

Pensamiento extraño
fuente
También me gustaría su opinión sobre lo siguiente: ¿Por qué usaría el patrón de visitante sobre una clase de utilidad? Puedo llamar a mi clase de utilidad de esta manera: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Cual es la diferencia ? ambos hacen separación de la preocupación ¿verdad? Espero que puedas ayudar.
j2emanue
@ j2emanue: No entiendo la pregunta. Te sugiero que lo desarrolles y lo publiques como una pregunta completa para que cualquiera pueda responder.
Pensamiento extraño
1
publiqué
14

Hay al menos tres muy buenas razones para usar el Patrón de visitante:

  1. Reduzca la proliferación de código, que es solo ligeramente diferente cuando cambian las estructuras de datos.

  2. Aplique 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.

Por favor, eche un vistazo a un artículo que he escrito sobre esto .

Richard Gomes
fuente
1
Comenté tu artículo con el mayor uso que he visto para el visitante. Pensamientos?
George Mauer
13

Como Konrad Rudolph ya señaló, es adecuado para casos en los que necesitamos un despacho doble

Aquí hay un ejemplo para mostrar una situación en la que necesitamos un doble envío y cómo el visitante nos ayuda a hacerlo.

Ejemplo:

Digamos que tengo 3 tipos de dispositivos móviles: iPhone, Android, Windows Mobile.

Todos estos tres dispositivos tienen una radio Bluetooth instalada en ellos.

Supongamos que la radio bluetooth puede ser de 2 OEM independientes: Intel y Broadcom.

Solo para hacer que el ejemplo sea relevante para nuestra discusión, supongamos también que las exposiciones de API por radio Intel son diferentes de las expuestas por la radio Broadcom.

Así es como se ven mis clases:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Ahora, me gustaría presentar una operación: encender el Bluetooth en un dispositivo móvil.

Su firma de función debería gustar algo como esto:

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

Entonces, dependiendo del tipo correcto de dispositivo y dependiendo del tipo correcto de radio Bluetooth , se puede encender llamando a los pasos o algoritmo apropiados .

En principio, se convierte en una matriz de 3 x 2, donde estoy tratando de vectorizar la operación correcta dependiendo del tipo correcto de objetos involucrados.

Un comportamiento polimórfico que depende del tipo de ambos argumentos.

ingrese la descripción de la imagen aquí

Ahora, el patrón de visitante se puede aplicar a este problema. La inspiración proviene de la página de Wikipedia que dice: “En esencia, el visitante permite agregar nuevas funciones virtuales a una familia de clases sin modificar las clases mismas; en su lugar, uno crea una clase de visitante que implementa todas las especializaciones apropiadas de la función virtual. El visitante toma la referencia de instancia como entrada e implementa el objetivo a través del envío doble ".

El envío doble es una necesidad aquí debido a la matriz 3x2

Así es como se verá la configuración: ingrese la descripción de la imagen aquí

Escribí el ejemplo para responder otra pregunta, el código y su explicación se mencionan aquí .

Kapoor
fuente
9

Lo encontré más fácil en los siguientes enlaces:

En http://www.remondo.net/visitor-pattern-example-csharp/ encontré un ejemplo que muestra un ejemplo simulado que muestra cuál es el beneficio del patrón de visitante. Aquí tiene diferentes clases de contenedor para Pill:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

Como puede ver arriba, BilsterPackcontiene pares de píldoras, por lo que debe multiplicar el número de pares por 2. Además, puede notar que el Bottleuso unites diferente y necesita ser lanzado.

Entonces, en el método principal, puede calcular el conteo de pastillas usando el siguiente código:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

Tenga en cuenta que el código anterior viola Single Responsibility Principle. Eso significa que debe cambiar el código del método principal si agrega un nuevo tipo de contenedor. También hacer que el cambio sea más largo es una mala práctica.

Entonces, al introducir el siguiente código:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Trasladó la responsabilidad de contar el número de Pills a la clase llamada PillCountVisitor(y eliminamos la declaración de cambio de caso). Eso significa que siempre que necesite agregar un nuevo tipo de contenedor de píldoras, debe cambiar solo la PillCountVisitorclase. También IVisitortenga en cuenta que la interfaz es general para usar en otros escenarios.

Al agregar el método Accept a la clase de contenedor de pastillas:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

Permitimos que los visitantes visiten las clases de envases de pastillas.

Al final calculamos el conteo de pastillas usando el siguiente código:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Eso significa: cada envase de píldoras permite al PillCountVisitorvisitante ver el conteo de sus píldoras. Él sabe contar las pastillas.

En el visitor.Counttiene el valor de las pastillas.

En http://butunclebob.com/ArticleS.UncleBob.IuseVisitor , ve un escenario real en el que no puede usar el polimorfismo (la respuesta) para seguir el Principio de responsabilidad única. De hecho en:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

el reportQtdHoursAndPaymétodo es para informar y representar y esto viola el Principio de responsabilidad única. Por lo tanto, es mejor usar el patrón de visitante para superar el problema.

Seyed Morteza Mousavi
fuente
2
Hola Sayed, ¿puedes editar tu respuesta para agregar las partes que te resultaron más esclarecedoras? SO generalmente desalienta las respuestas de solo enlace ya que el objetivo es ser una base de datos de conocimiento y los enlaces se caen.
George Mauer
8

El envío doble es solo una razón entre otras para usar este patrón .
Pero tenga en cuenta que es la única forma de implementar el envío doble o más en idiomas que utiliza un paradigma de envío único.

Aquí hay razones para usar el patrón:

1) Queremos definir nuevas operaciones sin cambiar el modelo en cada momento porque el modelo no cambia a menudo, mientras que las operaciones cambian con frecuencia.

2) No queremos unir modelo y comportamiento porque queremos tener un modelo reutilizable en múltiples aplicaciones o queremos tener un modelo extensible que permita a las clases de clientes definir sus comportamientos con sus propias clases.

3) Tenemos operaciones comunes que dependen del tipo concreto del modelo, pero no queremos implementar la lógica en cada subclase, ya que explotaría la lógica común en múltiples clases y, por lo tanto, en múltiples lugares .

4) Estamos utilizando un diseño de modelo de dominio y las clases de modelo de la misma jerarquía realizan demasiadas cosas distintas que podrían reunirse en otro lugar .

5) Necesitamos un doble despacho .
Tenemos variables declaradas con tipos de interfaz y queremos poder procesarlas de acuerdo con su tipo de tiempo de ejecución ... por supuesto sin usar if (myObj instanceof Foo) {}ni ningún truco.
La idea es, por ejemplo, pasar estas variables a métodos que declaran un tipo concreto de la interfaz como parámetro para aplicar un procesamiento específico. Esta forma de hacerlo no es posible desde el primer momento, ya que los idiomas se basan en un único envío porque la invocación elegida en tiempo de ejecución depende solo del tipo de tiempo de ejecución del receptor.
Tenga en cuenta que en Java, el método (firma) para llamar se elige en tiempo de compilación y depende del tipo declarado de los parámetros, no de su tipo de tiempo de ejecución.

El último punto que es una razón para usar el visitante también es una consecuencia porque a medida que implementa el visitante (por supuesto, para idiomas que no admiten el envío múltiple), necesariamente debe introducir una implementación de envío doble.

Tenga en cuenta que el recorrido de elementos (iteración) para aplicar el visitante en cada uno no es una razón para usar el patrón.
Utiliza el patrón porque divide el modelo y el procesamiento.
Y al usar el patrón, se beneficia además de una capacidad de iterador.
Esta capacidad es muy poderosa y va más allá de la iteración en el tipo común con un método específico como accept()es un método genérico.
Es un caso de uso especial. Así que lo pondré a un lado.


Ejemplo en Java

Ilustraré el valor agregado del patrón con un ejemplo de ajedrez en el que nos gustaría definir el procesamiento cuando el jugador solicita que se mueva una pieza.

Sin el uso del patrón visitante, podríamos definir comportamientos de movimiento de piezas directamente en las subclases de piezas.
Podríamos tener, por ejemplo, una Pieceinterfaz como:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

Cada subclase de pieza lo implementaría como:

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

Y lo mismo para todas las subclases de piezas.
Aquí hay una clase de diagrama que ilustra este diseño:

[diagrama de clase de modelo

Este enfoque presenta tres inconvenientes importantes:

- comportamientos como performMove()o computeIfKingCheck()muy probablemente usarán una lógica común.
Por ejemplo, cualquiera que sea el concreto Piece, performMove()finalmente establecerá la pieza actual en una ubicación específica y potencialmente tomará la pieza oponente.
Dividir comportamientos relacionados en múltiples clases en lugar de reunirlos derrota de alguna manera el patrón de responsabilidad única. Haciendo más difícil su mantenibilidad.

- procesar como checkMoveValidity()no debería ser algo que las Piecesubclases puedan ver o cambiar.
Es un control que va más allá de las acciones humanas o informáticas. Esta verificación se realiza en cada acción solicitada por un jugador para garantizar que el movimiento de la pieza solicitada sea válido.
Por lo tanto, ni siquiera queremos proporcionar eso en la Pieceinterfaz.

- En los juegos de ajedrez desafiantes para los desarrolladores de bot, generalmente la aplicación proporciona una API estándar ( Pieceinterfaces, subclases, tablero, comportamientos comunes, etc.) y permite a los desarrolladores enriquecer su estrategia de bot.
Para poder hacer eso, tenemos que proponer un modelo donde los datos y los comportamientos no estén estrechamente acoplados en las Pieceimplementaciones.

¡Así que vamos a usar el patrón de visitante!

Tenemos dos tipos de estructura:

- las clases modelo que aceptan ser visitadas (las piezas)

- los visitantes que los visitan (operaciones de mudanza)

Aquí hay un diagrama de clase que ilustra el patrón:

ingrese la descripción de la imagen aquí

En la parte superior tenemos los visitantes y en la parte inferior tenemos las clases modelo.

Aquí está la PieceMovingVisitorinterfaz (comportamiento especificado para cada tipo de Piece):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

La pieza se define ahora:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

Su método clave es:

void accept(PieceMovingVisitor pieceVisitor);

Proporciona el primer despacho: una invocación basada en el Piecereceptor.
En tiempo de compilación, el método está vinculado al accept()método de la interfaz Piece y en tiempo de ejecución, el método acotado se invocará en la Piececlase de tiempo de ejecución .
Y es la accept()implementación del método la que realizará un segundo envío.

De hecho, cada Piecesubclase que quiere ser visitada por un PieceMovingVisitorobjeto invoca el PieceMovingVisitor.visit()método pasando como argumento en sí.
De esta manera, el compilador limita tan pronto como el tiempo de compilación, el tipo del parámetro declarado con el tipo concreto.
Existe el segundo despacho.
Aquí está la Bishopsubclase que ilustra eso:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

Y aquí un ejemplo de uso:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

Inconvenientes de los visitantes

El patrón Visitor es un patrón muy poderoso, pero también tiene algunas limitaciones importantes que debe considerar antes de usarlo.

1) Riesgo de reducir / romper la encapsulación

En algunos tipos de operaciones, el patrón de visitante puede reducir o romper la encapsulación de objetos de dominio.

Por ejemplo, como la MovePerformingVisitor clase necesita establecer las coordenadas de la pieza real, la Pieceinterfaz debe proporcionar una forma de hacerlo:

void setCoordinates(Coordinates coordinates);

La responsabilidad de los Piececambios de coordenadas ahora está abierta a otras clases además de las Piecesubclases.
Mover el procesamiento realizado por el visitante en las Piecesubclases tampoco es una opción.
De hecho, creará otro problema ya que Piece.accept()acepta la implementación de cualquier visitante. No sabe qué realiza el visitante y, por lo tanto, no tiene idea de si y cómo cambiar el estado de la Pieza.
Una forma de identificar al visitante sería realizar un procesamiento posterior de Piece.accept()acuerdo con la implementación del visitante. Sería una muy mala idea, ya que crearía un alto acoplamiento entre las implementaciones de Visitor y las subclases de Piece y, además, probablemente requeriría usar truco como getClass(), instanceofo cualquier marcador que identifique la implementación de Visitor.

2) Requisito para cambiar el modelo

Contrariamente a algunos otros patrones de diseño de comportamiento como, Decoratorpor ejemplo, el patrón de visitante es intrusivo.
De hecho, necesitamos modificar la clase de receptor inicial para proporcionar un accept()método para aceptar ser visitado.
No tuvimos ningún problema Piecey sus subclases, ya que estas son nuestras clases .
En las clases integradas o de terceros, las cosas no son tan fáciles.
Necesitamos envolverlos o heredarlos (si podemos) para agregar el accept()método.

3) Indirecciones

El patrón crea múltiples indirecciones.
El envío doble significa dos invocaciones en lugar de una sola:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

Y podríamos tener indirecciones adicionales a medida que el visitante cambia el estado del objeto visitado.
Puede parecer un ciclo:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)
davidxxx
fuente
6

Cay Horstmann tiene un gran ejemplo de dónde aplicar Visitor en su libro de diseño y patrones OO . Él resume el problema:

Los objetos compuestos a menudo tienen una estructura compleja, compuesta de elementos individuales. Algunos elementos pueden tener nuevamente elementos secundarios. ... Una operación en un elemento visita sus elementos secundarios, les aplica la operación y combina los resultados. ... Sin embargo, no es fácil agregar nuevas operaciones a dicho diseño.

La razón por la que no es fácil es porque las operaciones se agregan dentro de las propias clases de estructura. Por ejemplo, imagine que tiene un Sistema de archivos:

Diagrama de clases del sistema de archivos

Aquí hay algunas operaciones (funcionalidades) que podríamos querer implementar con esta estructura:

  • Mostrar los nombres de los elementos del nodo (una lista de archivos)
  • Muestra el tamaño calculado de los elementos del nodo (donde el tamaño de un directorio incluye el tamaño de todos sus elementos secundarios)
  • etc.

Puede agregar funciones a cada clase en el FileSystem para implementar las operaciones (y la gente lo ha hecho en el pasado ya que es muy obvio cómo hacerlo). El problema es que cada vez que agrega una nueva funcionalidad (la línea "etc." anterior), es posible que necesite agregar más y más métodos a las clases de estructura. En algún momento, después de cierto número de operaciones que ha agregado a su software, los métodos en esas clases ya no tienen sentido en términos de cohesión funcional de las clases. Por ejemplo, FileNodetiene un método que tiene un método calculateFileColorForFunctionABC()para implementar la última funcionalidad de visualización en el sistema de archivos.

El patrón de visitante (como muchos patrones de diseño) nació del dolor y el sufrimiento de los desarrolladores que sabían que había una mejor manera de permitir que su código cambiara sin requerir muchos cambios en todas partes y también respetando los buenos principios de diseño (alta cohesión, bajo acoplamiento ) Es mi opinión que es difícil entender la utilidad de muchos patrones hasta que sientas ese dolor. Explicar el dolor (como intentamos hacer más arriba con las funcionalidades "etc." que se agregan) ocupa espacio en la explicación y es una distracción. Comprender los patrones es difícil por este motivo.

Visitor nos permite desacoplar las funcionalidades en la estructura de datos (por ejemplo, FileSystemNodes) de las estructuras de datos en sí. El patrón permite que el diseño respete la cohesión: las clases de estructura de datos son más simples (tienen menos métodos) y también las funcionalidades se encapsulan en Visitorimplementaciones. Esto se hace a través de doble despacho (que es la parte complicada del patrón): usando accept()métodos en las clases de estructura y visitX()métodos en las clases Visitor (la funcionalidad):

Diagrama de clases de FileSystem con visitante aplicado

Esta estructura nos permite agregar nuevas funcionalidades que funcionan en la estructura como visitantes concretos (sin cambiar las clases de estructura).

Diagrama de clases de FileSystem con visitante aplicado

Por ejemplo, un PrintNameVisitorque implementa la funcionalidad de listado de directorios y otro PrintSizeVisitorque implementa la versión con el tamaño. Podríamos imaginar un día tener un 'ExportXMLVisitor` que genere los datos en XML, u otro visitante que los genere en JSON, etc. Incluso podríamos tener un visitante que muestre mi árbol de directorios usando un lenguaje gráfico como DOT , para visualizar con otro programa

Como nota final: la complejidad de Visitor con su doble despacho significa que es más difícil de entender, codificar y depurar. En resumen, tiene un alto factor geek y va en contra del principio KISS. En una encuesta realizada por investigadores, se demostró que Visitor era un patrón controvertido (no había consenso sobre su utilidad). Algunos experimentos incluso mostraron que no hacía que el código fuera más fácil de mantener.

Fuhrmanator
fuente
La estructura del directorio creo que es un buen patrón compuesto, pero estoy de acuerdo con su último párrafo.
zar
5

En mi opinión, la cantidad de trabajo para agregar una nueva operación es más o menos la misma usando Visitor Patterno modificando directamente la estructura de cada elemento. Además, si tuviera que agregar una nueva clase de elemento, por ejemplo Cow, la interfaz Operación se verá afectada y esto se propagará a toda la clase de elementos existente, por lo que requerirá la recompilación de todas las clases de elementos. Entonces, ¿cuál es el punto?

kaosad
fuente
44
Casi cada vez que he usado Visitor es cuando estás trabajando para atravesar una jerarquía de objetos. Considere un menú de árbol anidado. Desea contraer todos los nodos. Si no implementa el visitante, debe escribir el código transversal del gráfico. O con el visitante: rootElement.visit (node) -> node.collapse(). Con el visitante, cada nodo implementa el recorrido del gráfico para todos sus elementos secundarios para que haya terminado.
George Mauer
@GeorgeMauer, el concepto de doble despacho me aclaró: la lógica dependiente del tipo es con el tipo o, el mundo del dolor. La idea de distribuir la lógica transversal todavía me da pausa. ¿Es más eficiente? ¿Es más fácil de mantener? ¿Qué sucede si se agrega "doblar al nivel N" como requisito?
nik.shornikov
@ nik.shornikov eficiencia realmente no debería ser una preocupación aquí. En casi cualquier idioma, algunas llamadas a funciones son insignificantes. Cualquier cosa más allá de eso es micro-optimización. ¿Es más fácil de mantener? Bueno, eso depende. Creo que la mayoría de las veces lo es, a veces no lo es. En cuanto a "doblar al nivel N". Pase fácil en un levelsRemainingcontador como parámetro. Disminuya antes de llamar al siguiente nivel de niños. Dentro de tu visitante if(levelsRemaining == 0) return.
George Mauer
1
@GeorgeMauer, totalmente de acuerdo en que la eficiencia es una preocupación menor. Pero la mantenibilidad, por ejemplo, anulaciones de la firma de aceptación, es exactamente lo que creo que la decisión debería reducirse.
nik.shornikov
5

Patrón de visitante como la misma implementación subterránea para la programación de Objetos de aspecto.

Por ejemplo, si define una nueva operación sin cambiar las clases de los elementos en los que opera

mixz
fuente
por mencionar la Programación de Objetos de Aspecto
milesma
5

Descripción rápida del patrón de visitante. Todas las clases que requieren modificación deben implementar el método 'aceptar'. Los clientes llaman a este método de aceptación para realizar alguna acción nueva en esa familia de clases, extendiendo así su funcionalidad. Los clientes pueden utilizar este método de aceptación para realizar una amplia gama de nuevas acciones pasando una clase de visitante diferente para cada acción específica. Una clase de visitante contiene múltiples métodos de visita anulados que definen cómo lograr esa misma acción específica para cada clase dentro de la familia. Estos métodos de visita pasan una instancia en la que trabajar.

Cuando podrías considerar usarlo

  1. Cuando tienes una familia de clases sabes que vas a tener que agregar muchas nuevas acciones a todas, pero por alguna razón no puedes alterar o recompilar la familia de clases en el futuro.
  2. Cuando desee agregar una nueva acción y tener esa nueva acción completamente definida dentro de una, la clase de visitante en lugar de extenderse a través de múltiples clases.
  3. ¡Cuando tu jefe te dice que debes producir una variedad de clases que deben hacer algo ahora mismo ! ... pero en realidad nadie sabe exactamente qué es ese algo todavía.
Andrew Paté
fuente
4

No entendí este patrón hasta que me encontré con el artículo del tío Bob y leí los comentarios. Considere el siguiente código:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

Si bien puede verse bien, ya que confirma la responsabilidad única , viola el principio abierto / cerrado . Cada vez que tenga un nuevo tipo de Empleado, deberá agregarlo con la verificación de tipo. Y si no lo haces, nunca lo sabrás en tiempo de compilación.

Con el patrón de visitante puede hacer que su código sea más limpio, ya que no viola el principio abierto / cerrado y no viola la responsabilidad individual. Y si olvida implementar la visita, no se compilará:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

La magia es que si bien se v.Visit(this)ve igual, de hecho es diferente, ya que llama a diferentes sobrecargas de visitantes.

Acceso denegado
fuente
Sí, específicamente lo encuentro útil cuando trabajo con estructuras de árbol, no solo listas planas (las listas planas serían un caso especial de un árbol). Como notará, no es terriblemente desordenado solo en las listas, pero el visitante puede ser un salvador a medida que la navegación entre nodos se vuelve más compleja
George Mauer
3

Basado en la excelente respuesta de @Federico A. Ramponi.

Solo imagina que tienes esta jerarquía:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

¿Qué sucede si necesita agregar un método "Walk" aquí? Eso será doloroso para todo el diseño.

Al mismo tiempo, agregar el método "Caminar" genera nuevas preguntas. ¿Qué pasa con "comer" o "dormir"? ¿Realmente debemos agregar un nuevo método a la jerarquía Animal para cada nueva acción u operación que queramos agregar? Eso es feo y lo más importante, nunca podremos cerrar la interfaz Animal. Entonces, con el patrón de visitante, ¡podemos agregar un nuevo método a la jerarquía sin modificar la jerarquía!

Entonces, solo verifique y ejecute este ejemplo de C #:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
Tomás Escamez
fuente
caminar, comer no son ejemplos adecuados ya que son comunes tanto para ambos Dogcomo para ellos Cat. Podrías haberlos hecho en la clase base para que se hereden o elegir un ejemplo adecuado.
Abhinav Gauniyal
aunque los sonidos son diferentes, buena muestra, pero no estoy seguro si tiene algo que ver con el patrón de visitante
DAG
3

Visitante

Visitor permite agregar nuevas funciones virtuales a una familia de clases sin modificar las clases mismas; en cambio, uno crea una clase de visitante que implementa todas las especializaciones apropiadas de la función virtual

Estructura del visitante:

ingrese la descripción de la imagen aquí

Use el patrón de visitante si:

  1. Se deben realizar operaciones similares en objetos de diferentes tipos agrupados en una estructura
  2. Necesita ejecutar muchas operaciones distintas y no relacionadas. Separa la operación de la estructura de los objetos.
  3. Se deben agregar nuevas operaciones sin cambios en la estructura del objeto
  4. Reúna las operaciones relacionadas en una sola clase en lugar de obligarlo a cambiar o derivar clases
  5. Agregue funciones a las bibliotecas de clases para las que no tiene la fuente o no puede cambiar la fuente

Aunque el patrón Visitor proporciona flexibilidad para agregar una nueva operación sin cambiar el código existente en Object, esta flexibilidad tiene un inconveniente.

Si se ha agregado un nuevo objeto Visitable, se requieren cambios de código en las clases Visitor & ConcreteVisitor . Hay una solución alternativa para abordar este problema: use la reflexión, que tendrá un impacto en el rendimiento.

Fragmento de código:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Explicación:

  1. Visitable( Element) es una interfaz y este método de interfaz debe agregarse a un conjunto de clases.
  2. Visitores una interfaz que contiene métodos para realizar una operación en Visitableelementos.
  3. GameVisitores una clase que implementa Visitorinterface ( ConcreteVisitor).
  4. Cada Visitableelemento acepta Visitore invoca un método relevante de Visitorinterfaz.
  5. Puedes tratar Gameas Elementy juegos concretos como Chess,Checkers and Ludoas ConcreteElements.

En el ejemplo anterior, Chess, Checkers and Ludohay tres juegos diferentes (y Visitableclases). En un buen día, me encontré con un escenario para registrar estadísticas de cada juego. Por lo tanto, sin modificar la clase individual para implementar la funcionalidad de estadísticas, puede centralizar esa responsabilidad en GameVisitorclase, lo que hace el truco para usted sin modificar la estructura de cada juego.

salida:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Referirse a

artículo de diseño

artículo de creación de fuente

para más detalles

Decorador

El patrón permite agregar comportamiento a un objeto individual, ya sea estática o dinámicamente, sin afectar el comportamiento de otros objetos de la misma clase

Artículos Relacionados:

Patrón de decorador para IO

¿Cuándo usar el patrón decorador?

Ravindra babu
fuente
2

Realmente me gusta la descripción y el ejemplo de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

Se supone que tiene una jerarquía de clases primaria que es fija; quizás sea de otro proveedor y no pueda realizar cambios en esa jerarquía. Sin embargo, su intención es que le gustaría agregar nuevos métodos polimórficos a esa jerarquía, lo que significa que normalmente tendría que agregar algo a la interfaz de la clase base. Entonces, el dilema es que necesita agregar métodos a la clase base, pero no puede tocar la clase base. ¿Cómo resuelves esto?

El patrón de diseño que resuelve este tipo de problema se llama "visitante" (el último en el libro de Patrones de diseño), y se basa en el esquema de despacho doble que se muestra en la última sección.

El patrón de visitante le permite extender la interfaz del tipo primario creando una jerarquía de clases separada del tipo Visitor para virtualizar las operaciones realizadas en el tipo primario. Los objetos del tipo primario simplemente "aceptan" al visitante, luego llaman a la función miembro de enlace dinámico del visitante.

wojcikstefan
fuente
Si bien técnicamente es el patrón Visitor, esto es realmente un doble despacho básico de su ejemplo. Yo diría que la utilidad no es particularmente visible solo por esto.
George Mauer el
1

Si bien he entendido el cómo y el cuándo, nunca he entendido el por qué. En caso de que ayude a alguien con experiencia en un lenguaje como C ++, desea leer esto con mucho cuidado.

Para los perezosos, utilizamos el patrón de visitante porque "mientras las funciones virtuales se envían dinámicamente en C ++, la sobrecarga de funciones se realiza de forma estática" .

O, dicho de otro modo, para asegurarse de que se llama CollideWith (ApolloSpacecraft &) cuando pasa una referencia de SpaceShip que está realmente vinculada a un objeto ApolloSpacecraft.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}
Carl
fuente
2
El uso del despacho dinámico en el patrón de visitante me deja completamente perplejo. Los usos sugeridos del patrón describen la ramificación que se puede hacer en tiempo de compilación. Aparentemente, estos casos estarían mejor con una plantilla de función.
Praxeolítico
0

Gracias por la increíble explicación de @Federico A. Ramponi , acabo de hacer esto en versión java . Espero que pueda ser útil.

Además, tal como lo señaló @Konrad Rudolph , en realidad es un despacho doble que usa dos instancias concretas juntas para determinar los métodos de tiempo de ejecución.

Por lo tanto, en realidad no hay necesidad de crear una interfaz común para el ejecutor de la operación siempre que tengamos la interfaz de operación correctamente definida.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

Como es de esperar, una interfaz común nos brindará más claridad, aunque en realidad no es la parte esencial de este patrón.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}
Hearen
fuente
0

Su pregunta es cuándo saber:

No codifico primero con el patrón de visitante. Codifico estándar y espero que ocurra la necesidad y luego refactorizo. digamos que tiene varios sistemas de pago que instaló uno a la vez. Al momento del pago, puede tener muchas condiciones if (o instanceOf), por ejemplo:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

Ahora imagina que tengo 10 métodos de pago, se pone un poco feo. Entonces, cuando ves que se produce ese tipo de patrón, el visitante es útil para separar todo eso y luego terminas llamando a algo así:

new PaymentCheckoutVistor(paymentType).visit()

Puede ver cómo implementarlo a partir de la cantidad de ejemplos aquí, solo le estoy mostrando un caso de uso.

j2emanue
fuente