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.
design-patterns
visitor-pattern
George Mauer
fuente
fuente
Respuestas:
No estoy muy familiarizado con el patrón Visitante. A ver si lo tengo bien. Supongamos que tienes una jerarquía de animales
(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:
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:
Luego, modificamos la jerarquía para aceptar nuevas operaciones:
Finalmente, implementamos la operación real, sin modificar ni Cat ni Dog :
Ahora tiene una manera de agregar operaciones sin modificar más la jerarquía. Así es como funciona:
fuente
letsDo(Operation *v)
necesita un punterotheSound.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.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 .
fuente
switch
:switch
codifica 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.virtual
que 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).Todos aquí están en lo correcto, pero creo que no aborda el "cuándo". Primero, de Patrones de diseño:
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.
fuente
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.]
El
visit
mé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 delvisit
método para hacer un pedido ascendente o de otro tipo.Aquí hay una superclase para visitantes. Se usa por el
visit
método. "Llega" a cada nodo en la estructura. Como elvisit
método llamaup
ydown
, el visitante puede realizar un seguimiento de la profundidad.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 unaVisitor
,dumpNodes
.Luego aplica el
dumpNodes
al árbol. EldumpNode
objeto "visitará" cada nodo en el árbol.El
visit
algoritmo TreeNode asegurará que cada TreeNode se use como argumento delarrivedAt
método del Visitante .fuente
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).
fuente
Hay al menos tres muy buenas razones para usar el Patrón de visitante:
Reduzca la proliferación de código, que es solo ligeramente diferente cuando cambian las estructuras de datos.
Aplique el mismo cálculo a varias estructuras de datos, sin cambiar el código que implementa el cálculo.
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 .
fuente
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:
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:
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.
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:
Escribí el ejemplo para responder otra pregunta, el código y su explicación se mencionan aquí .
fuente
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
:Como puede ver arriba,
BilsterPack
contiene pares de píldoras, por lo que debe multiplicar el número de pares por 2. Además, puede notar que elBottle
usounit
es diferente y necesita ser lanzado.Entonces, en el método principal, puede calcular el conteo de pastillas usando el siguiente código:
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:
Trasladó la responsabilidad de contar el número de
Pill
s a la clase llamadaPillCountVisitor
(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 laPillCountVisitor
clase. TambiénIVisitor
tenga 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:
Permitimos que los visitantes visiten las clases de envases de pastillas.
Al final calculamos el conteo de pastillas usando el siguiente código:
Eso significa: cada envase de píldoras permite al
PillCountVisitor
visitante ver el conteo de sus píldoras. Él sabe contar las pastillas.En el
visitor.Count
tiene 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:
el
reportQtdHoursAndPay
mé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.fuente
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
Piece
interfaz como:Cada subclase de pieza lo implementaría como:
Y lo mismo para todas las subclases de piezas.
Aquí hay una clase de diagrama que ilustra este diseño:
Este enfoque presenta tres inconvenientes importantes:
- comportamientos como
performMove()
ocomputeIfKingCheck()
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 lasPiece
subclases 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
Piece
interfaz.- En los juegos de ajedrez desafiantes para los desarrolladores de bot, generalmente la aplicación proporciona una API estándar (
Piece
interfaces, 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
Piece
implementaciones.¡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:
En la parte superior tenemos los visitantes y en la parte inferior tenemos las clases modelo.
Aquí está la
PieceMovingVisitor
interfaz (comportamiento especificado para cada tipo dePiece
):La pieza se define ahora:
Su método clave es:
Proporciona el primer despacho: una invocación basada en el
Piece
receptor.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 laPiece
clase de tiempo de ejecución .Y es la
accept()
implementación del método la que realizará un segundo envío.De hecho, cada
Piece
subclase que quiere ser visitada por unPieceMovingVisitor
objeto invoca elPieceMovingVisitor.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
Bishop
subclase que ilustra eso:Y aquí un ejemplo de uso:
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, laPiece
interfaz debe proporcionar una forma de hacerlo:La responsabilidad de los
Piece
cambios de coordenadas ahora está abierta a otras clases además de lasPiece
subclases.Mover el procesamiento realizado por el visitante en las
Piece
subclases 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 comogetClass()
,instanceof
o 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,
Decorator
por 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
Piece
y 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:
Y podríamos tener indirecciones adicionales a medida que el visitante cambia el estado del objeto visitado.
Puede parecer un ciclo:
fuente
Cay Horstmann tiene un gran ejemplo de dónde aplicar Visitor en su libro de diseño y patrones OO . Él resume el problema:
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:
Aquí hay algunas operaciones (funcionalidades) que podríamos querer implementar con esta estructura:
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,
FileNode
tiene un método que tiene un métodocalculateFileColorForFunctionABC()
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 enVisitor
implementaciones. Esto se hace a través de doble despacho (que es la parte complicada del patrón): usandoaccept()
métodos en las clases de estructura yvisitX()
métodos en las clases Visitor (la funcionalidad):Esta estructura nos permite agregar nuevas funcionalidades que funcionan en la estructura como visitantes concretos (sin cambiar las clases de estructura).
Por ejemplo, un
PrintNameVisitor
que implementa la funcionalidad de listado de directorios y otroPrintSizeVisitor
que 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 programaComo 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.
fuente
En mi opinión, la cantidad de trabajo para agregar una nueva operación es más o menos la misma usando
Visitor Pattern
o modificando directamente la estructura de cada elemento. Además, si tuviera que agregar una nueva clase de elemento, por ejemploCow
, 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?fuente
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.levelsRemaining
contador como parámetro. Disminuya antes de llamar al siguiente nivel de niños. Dentro de tu visitanteif(levelsRemaining == 0) return
.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
fuente
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
fuente
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:
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á:
La magia es que si bien se
v.Visit(this)
ve igual, de hecho es diferente, ya que llama a diferentes sobrecargas de visitantes.fuente
Basado en la excelente respuesta de @Federico A. Ramponi.
Solo imagina que tienes esta jerarquía:
¿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 #:
fuente
Dog
como para ellosCat
. Podrías haberlos hecho en la clase base para que se hereden o elegir un ejemplo adecuado.Visitante
Estructura del visitante:
Use el patrón de visitante si:
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:
Explicación:
Visitable
(Element
) es una interfaz y este método de interfaz debe agregarse a un conjunto de clases.Visitor
es una interfaz que contiene métodos para realizar una operación enVisitable
elementos.GameVisitor
es una clase que implementaVisitor
interface (ConcreteVisitor
).Visitable
elemento aceptaVisitor
e invoca un método relevante deVisitor
interfaz.Game
asElement
y juegos concretos comoChess,Checkers and Ludo
asConcreteElements
.En el ejemplo anterior,
Chess, Checkers and Ludo
hay tres juegos diferentes (yVisitable
clases). 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 enGameVisitor
clase, lo que hace el truco para usted sin modificar la estructura de cada juego.salida:
Referirse a
artículo de diseño
artículo de creación de fuente
para más detalles
Decorador
Artículos Relacionados:
Patrón de decorador para IO
¿Cuándo usar el patrón decorador?
fuente
Realmente me gusta la descripción y el ejemplo de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .
fuente
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.
fuente
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.
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.
fuente
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:
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í:
Puede ver cómo implementarlo a partir de la cantidad de ejemplos aquí, solo le estoy mostrando un caso de uso.
fuente