Patrón de visitantes
En la programación orientada a objetos y la ingeniería de software, el patrón de diseño visitante es una forma de separar un algoritmo de una estructura de objeto en la que opera. Un resultado práctico de esta separación es la capacidad de agregar nuevas operaciones a estructuras de objetos existentes sin modificar las estructuras. Es una forma de seguir el principio abierto/cerrado.
En esencia, el visitante permite agregar nuevas funciones virtuales a una familia de clases, sin modificar las clases. En su lugar, se crea una clase de visitante que implementa todas las especializaciones apropiadas de la función virtual. El visitante toma la referencia de la instancia como entrada e implementa el objetivo a través del doble envío.
Resumen
El visitante El patrón de diseño es uno de los veintitrés patrones de diseño conocidos de Gang of Four que describen cómo resolver problemas de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar.
¿Qué problemas puede resolver el patrón de diseño Visitor?
- Debe ser posible definir una nueva operación para (algunos) clases de una estructura de objetos sin cambiar las clases.
Cuando se necesitan nuevas operaciones con frecuencia y la estructura del objeto consta de muchas clases no relacionadas, es inflexible agregar nuevas subclases cada vez que se requiere una nueva operación porque "[..] distribuir todas estas operaciones entre las diversas clases de nodos conduce a un sistema que es difícil de entender, mantener y cambiar."
¿Qué solución describe el patrón de diseño Visitor?
- Define un objeto separado (visitor) que implementa una operación que se realizará sobre elementos de una estructura de objeto.
- Los clientes atraviesan la estructura del objeto y llaman a operación de envío acepta (visitor) en un elemento - que "despacha" (delegados) la solicitud al "objeto de visitante aceptado". El objeto visitante realiza la operación en el elemento ("visita el elemento").
Esto hace posible crear nuevas operaciones independientemente de las clases de una estructura de objeto agregando nuevos objetos de visitante.
Vea también la clase UML y el diagrama de secuencia a continuación.
Definición
La Banda de los Cuatro define al Visitante como:
Representar una operación a realizar sobre elementos de una estructura de objetos. El visitante le permite definir una nueva operación sin cambiar las clases de los elementos en los que opera.
La naturaleza del Visitante lo convierte en un patrón ideal para conectarse a las API públicas, lo que permite a sus clientes realizar operaciones en una clase utilizando un "visitante" clase sin tener que modificar la fuente.
Ventajas
Mover operaciones a clases de visitantes es beneficioso cuando
- son necesarias muchas operaciones no relacionadas con una estructura de objetos,
- las clases que componen la estructura del objeto se conocen y no se espera que cambien,
- es necesario añadir con frecuencia nuevas operaciones,
- un algoritmo implica varias clases de la estructura del objeto, pero se desea administrarlo en una sola ubicación,
- un algoritmo necesita trabajar a través de varias jerarquías de clase independientes.
Un inconveniente de este patrón, sin embargo, es que hace que las extensiones a la jerarquía de clases sean más difíciles, ya que las nuevas clases generalmente requieren que se agregue un nuevo método visit
a cada visitante.
Solicitud
Considere el diseño de un sistema de diseño asistido por computadora (CAD) 2D. En esencia, hay varios tipos para representar formas geométricas básicas como círculos, líneas y arcos. Las entidades se ordenan en capas, y en la parte superior de la jerarquía de tipos se encuentra el dibujo, que es simplemente una lista de capas, más algunas propiedades añadidas.
Una operación fundamental en esta jerarquía de tipos es guardar un dibujo en el formato de archivo nativo del sistema. A primera vista, puede parecer aceptable agregar métodos de guardado locales a todos los tipos en la jerarquía. Pero también es útil poder guardar dibujos en otros formatos de archivo. Agregar cada vez más métodos para guardar en muchos formatos de archivo diferentes pronto desordena la estructura de datos geométricos original relativamente pura.
Una forma ingenua de resolver esto sería mantener funciones separadas para cada formato de archivo. Tal función de guardado tomaría un dibujo como entrada, lo recorrería y codificaría en ese formato de archivo específico. Como esto se hace para cada formato diferente agregado, se acumula la duplicación entre las funciones. Por ejemplo, guardar una forma de círculo en un formato de trama requiere un código muy similar sin importar qué forma de trama específica se utilice, y es diferente de otras formas primitivas. El caso de otras formas primitivas como líneas y polígonos es similar. Por lo tanto, el código se convierte en un gran bucle exterior que atraviesa los objetos, con un gran árbol de decisiones dentro del bucle que consulta el tipo de objeto. Otro problema con este enfoque es que es muy fácil perder una forma en uno o más protectores, o se introduce una nueva forma primitiva, pero la rutina de guardado se implementa solo para un tipo de archivo y no para otros, lo que lleva a la extensión y el mantenimiento del código. problemas. A medida que crecen las versiones de un mismo archivo se vuelve más complicado mantenerlo.
En su lugar, se puede aplicar el patrón de visitante. Codifica una operación lógica en toda la jerarquía en una clase que contiene un método por tipo. En el ejemplo de CAD, cada función de guardar se implementaría como una subclase Visitante separada. Esto eliminaría toda la duplicación de comprobaciones de tipos y pasos transversales. También haría que el compilador se quejara si se omite una forma.
Ciclos de iteración
El patrón de visitante se puede usar para la iteración sobre estructuras de datos similares a contenedores, al igual que el patrón Iterator, pero con una funcionalidad limitada. Por ejemplo, la iteración sobre una estructura de directorio podría implementarse mediante una clase de función en lugar de un patrón de bucle más convencional. Esto permitiría derivar diversa información útil del contenido de los directorios mediante la implementación de una funcionalidad de visitante para cada elemento mientras se reutiliza el código de iteración. Se emplea ampliamente en los sistemas Smalltalk y también se puede encontrar en C++. Sin embargo, una desventaja de este enfoque es que no puede salir del bucle fácilmente o iterar simultáneamente (en paralelo, es decir, atravesar dos contenedores al mismo tiempo con un solo i
variable). Este último requeriría escribir una funcionalidad adicional para que un visitante admita estas características.
Estructura
Clase UML y diagrama de secuencia
En el diagrama de clases UML anterior, la clase ElementA
no implementa una nueva operación directamente.
En su lugar, ElementoA
implementa una operación de envío aceptar(visitante)
que "despacha" (delegados) una solicitud al "objeto de visitante aceptado" (visitante.visitElementA(esto)). La clase Visitor1
implementa la operación (visitElementA(e:ElementA)
).
ElementB
luego implementa accept(visitor)
enviando a visitor.visitElementB(this)
. La clase Visitor1
implementa la operación (visitElementB(e:ElementB)
).
El diagrama de secuencia UML
muestra las interacciones en tiempo de ejecución: el objeto Client
atraviesa los elementos de una estructura de objeto (ElementA,ElementB
) y llama a accept(visitor)
en cada elemento
Primero, el Client
llama a accept(visitor)
en
ElementA
, que llama a visitElementA(this)
en el objeto visitor
aceptado.
El elemento en sí mismo (this
) se pasa al visitante
para que
puede "visitar" ElementA
(llame a operationA()
).
A partir de entonces, el Client
llama a accept(visitor)
en
ElementoB
, que llama a visitElementB(this)
en el visitante
que "visita" ElementB
(llama a operationB()
).
Diagrama de clases
Detalles
El patrón de visitante requiere un lenguaje de programación que admita el envío único, como lo hacen los lenguajes comunes orientados a objetos (como C++, Java, Smalltalk, Objective-C, Swift, JavaScript, Python y C#). Bajo esta condición, considere dos objetos, cada uno de algún tipo de clase; uno se denomina elemento y el otro es visitante.
El visitante declara un método visit
, que toma el elemento como argumento, para cada clase de elemento. Los visitantes concretos se derivan de la clase visitante e implementan estos métodos visit
, cada uno de los cuales implementa parte del algoritmo que opera en la estructura del objeto. El estado del algoritmo es mantenido localmente por la clase visitante concreta.
El element declara un método accept
para aceptar un visitante, tomando al visitante como argumento. Elementos concretos, derivados de la clase de elemento, implementan el método accept
. En su forma más simple, esto no es más que una llamada al método visit
del visitante. Los elementos compuestos, que mantienen una lista de objetos secundarios, normalmente iteran sobre estos, llamando al método accept
de cada elemento secundario.
El cliente crea la estructura del objeto, directa o indirectamente, e instancia a los visitantes concretos. Cuando se va a realizar una operación que se implementa utilizando el patrón Visitor, llama al método accept
de los elementos de nivel superior.
Cuando se llama al método accept
en el programa, su implementación se elige en función tanto del tipo dinámico del elemento como del tipo estático del visitante. Cuando se llama al método visit
asociado, su implementación se elige en función del tipo dinámico del visitante y del tipo estático del elemento, como se conoce dentro de la implementación de accept
método, que es el mismo que el tipo dinámico del elemento. (Como beneficio adicional, si el visitante no puede manejar un argumento del tipo de elemento dado, el compilador detectará el error).
Por lo tanto, la implementación del método visit
se elige en función tanto del tipo dinámico del elemento como del tipo dinámico del visitante. Esto implementa efectivamente el doble despacho. Para los lenguajes cuyos sistemas de objetos admiten envíos múltiples, no solo envíos únicos, como Common Lisp o C# a través de Dynamic Language Runtime (DLR), la implementación del patrón de visitante se simplifica enormemente (también conocido como Dynamic Visitor) al permitir el uso de la función de sobrecarga simple para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que opere solo con datos públicos, se ajusta al principio abierto/cerrado (ya que no modifica las estructuras existentes) y al principio de responsabilidad única (ya que implementa el patrón Visitor en un componente separado).
De esta manera, se puede escribir un algoritmo para recorrer un gráfico de elementos, y se pueden realizar muchos tipos diferentes de operaciones durante ese recorrido proporcionando diferentes tipos de visitantes para interactuar con los elementos en función de los tipos dinámicos de ambos elementos y los visitantes.
Ejemplo de C#
Este ejemplo declara una clase ExpressionPrintingVisitor
separada que se encarga de la impresión.
namespace Wikipedia;público clase ExpressionPrintingVisitor{} público vacío PrintLiteral()Literal literal) {} Consola.WriteLine()literal.Valor); } público vacío Impresión()Adición Además) {} doble izquierda Valor = Además.Izquierda.GetValue(); doble derecho Valor = Además.Bien..GetValue(); Var suma = Además.GetValue(); Consola.WriteLine()"{0} + {1} = {2} ", izquierda Valor, derecho Valor, suma); }}público abstracto clase Expresión{} público abstracto vacío Aceptar()ExpressionPrintingVisitor v); público abstracto doble GetValue();}público clase Literal : Expresión{} público doble Valor {} #; set; } público Literal()doble valor) {} esto.Valor = valor; } público Anulación vacío Aceptar()ExpressionPrintingVisitor v) {} v.PrintLiteral()esto); } público Anulación doble GetValue() {} retorno Valor; }}público clase Adición : Expresión{} público Expresión Izquierda {} #; set; } público Expresión Bien. {} #; set; } público Adición()Expresión izquierda, Expresión derecho) {} Izquierda = izquierda; Bien. = derecho; } público Anulación vacío Aceptar()ExpressionPrintingVisitor v) {} Izquierda.Aceptar()v); Bien..Aceptar()v); v.Impresión()esto); } público Anulación doble GetValue() {} retorno Izquierda.GetValue() + Bien..GetValue(); }}público estática clase Programa{} público estática vacío Main()cuerda[] args) {} // Emular 1 + 2 + 3 Var e = nuevo Adición() nuevo Adición() nuevo Literal()1), nuevo Literal()2) ), nuevo Literal()3) ); Var impresión Visitante = nuevo ExpressionPrintingVisitor(); e.Aceptar()impresión Visitante); }}
Ejemplo de conversación pequeña
En este caso, es responsabilidad del objeto saber cómo imprimirse en una secuencia. El visitante aquí es entonces el objeto, no la corriente.
"No hay sintaxis para crear una clase. Las clases se crean enviando mensajes a otras clases".WriteStream subclase: #ExpresiónPrinter ejemplo Variable Nombres: ' ' claseVariable Nombres: ' ' paquete: Wikipedia '.ExpressionPrinter. anObject "Delega la acción al objeto. El objeto no necesita ser de ningún tipo clase; sólo necesita ser capaz de entender el mensaje #putOn:" anObject Ponte: auto. ^ anObject.Objeto subclase: #Expresión ejemplo Variable Nombres: ' ' claseVariable Nombres: ' ' paquete: Wikipedia '.Expresión subclase: #Literal ejemplo Variable Nombres: 'valor ' claseVariable Nombres: ' ' paquete: Wikipedia '.Literal class tituladawith: aValue "Método de clase para construir una instancia de la clase Literal" ^ auto nuevo valor: aValue; tu.Literal√≥value: aValue "Setter for value" valor := aValue.Literal> aStream "Un objeto Literal sabe cómo imprimirse" aStream SiguientePutAll: valor asString.Expresión subclase: #Addition ejemplo Variable Nombres: 'izquierda derecha ' claseVariable Nombres: ' ' paquete: Wikipedia '.Adición class título: a Bien. b "Método de clase para construir una instancia de la clase de adición" ^ auto nuevo izquierda: a; Bien. b; tu.Adición- ¿Qué? anExpresión "Setter para la izquierda" izquierda := anExpresión.Adición- No. anExpresión "Setter for right" derecho := anExpresión.Adición> aStream "Un objeto de adición sabe cómo imprimirse" aStream siguiente Ponga: $(. izquierda Ponte: aStream. aStream siguiente Ponga: $+. derecho Ponte: aStream. aStream siguiente Ponga: $).Objeto subclase: #Programa ejemplo Variable Nombres: ' ' claseVariable Nombres: ' ' paquete: Wikipedia '.Programa>principal Silencio expresión streaming Silencio expresión := Adición izquierda: ()Adición izquierda: ()Literal con: 1)
Bien. ()Literal con: 2)
Bien. ()Literal con: 3). streaming := ExpressionPrinter sobre: ()String nuevo: 100). streaming escribir: expresión. Transcripción Mostrar: streaming contenidos. Transcripción Flush.
Ir
Go no admite la sobrecarga, por lo que los métodos de visita necesitan nombres diferentes. Una interfaz de visitante típica podría ser
Tipo Visitante interfaz {}visita Rueda()rueda Rueda) cuerdavisitaEngine()motor Motor) cuerdavisitaBody()cuerpo Cuerpo) cuerdavisite()coche Car) cuerda}
Ejemplo de Java
El siguiente ejemplo está en el lenguaje Java y muestra cómo se puede imprimir el contenido de un árbol de nodos (en este caso, que describe los componentes de un automóvil). En lugar de crear métodos print
para cada subclase de nodo (Rueda
, Motor
, Cuerpo
y Coche
), una clase de visitante (CarElementPrintVisitor
) realiza la acción de impresión requerida. Debido a que las diferentes subclases de nodos requieren acciones ligeramente diferentes para imprimir correctamente, CarElementPrintVisitor
envía acciones basadas en la clase del argumento pasado a su método visit
. CarElementDoVisitor
, que es similar a una operación de guardar para un formato de archivo diferente, hace lo mismo.
Diagrama
Fuentes
importación Java.util. Lista;interfaz CarElement {} vacío aceptar()CarElementVisitor visitante);}interfaz CarElementVisitor {} vacío visita()Cuerpo cuerpo); vacío visita()Car coche); vacío visita()Motor motor); vacío visita()Rueda rueda);}clase Rueda implementos CarElement {} privado final String Nombre; público Rueda()final String Nombre) {} esto.Nombre = Nombre; } público String getName() {} retorno Nombre; } @Override público vacío aceptar()CarElementVisitor visitante) {} /* * aceptar(CarElementVisitor) en implementos de rueda * aceptar(CarElementVisitor) en CarElement, por lo que la llamada * aceptar está obligado a correr. Esto puede considerarse * el primer envío*. However, the decision to call * visit(Wheel) (a diferencia de visitar(Engine) etc.) * hecho durante el tiempo de compilación ya que 'esto' es conocido en compilación * tiempo para ser una rueda. Además, cada aplicación de * CarElementVisitor implementa la visita(Wheel), que es * otra decisión que se toma a tiempo. Esto puede ser * consideró el envío *segundo*. */ visitante.visita()esto); }}clase Cuerpo implementos CarElement {} @Override público vacío aceptar()CarElementVisitor visitante) {} visitante.visita()esto); }}clase Motor implementos CarElement {} @Override público vacío aceptar()CarElementVisitor visitante) {} visitante.visita()esto); }}clase Car implementos CarElement {} privado final Lista.CarElement■ elementos; público Car() {} esto.elementos = Lista.de() nuevo Rueda()"a la izquierda"), nuevo Rueda()"a la derecha"), nuevo Rueda()"a la izquierda"), nuevo Rueda()"derecho"), nuevo Cuerpo(), nuevo Motor() ); } @Override público vacío aceptar()CarElementVisitor visitante) {} para ()CarElement elemento : elementos) {} elemento.aceptar()visitante); } visitante.visita()esto); }}clase CarElementDoVisitor implementos CarElementVisitor {} @Override público vacío visita()Cuerpo cuerpo) {} Sistema.Fuera..println()"Moving my body"); } @Override público vacío visita()Car coche) {} Sistema.Fuera..println()"Iniciando mi coche"); } @Override público vacío visita()Rueda rueda) {} Sistema.Fuera..println()"Kicking my " + rueda.getName() + "Llanta"); } @Override público vacío visita()Motor motor) {} Sistema.Fuera..println()"Iniciando mi motor"); }}clase CarElementPrintVisitor implementos CarElementVisitor {} @Override público vacío visita()Cuerpo cuerpo) {} Sistema.Fuera..println()"Cuerpo visitante"); } @Override público vacío visita()Car coche) {} Sistema.Fuera..println()"Vino coche"); } @Override público vacío visita()Motor motor) {} Sistema.Fuera..println()"Motor visitante"); } @Override público vacío visita()Rueda rueda) {} Sistema.Fuera..println()"Visitando" + rueda.getName() + "Llanta"); }}público clase VisitorDemo {} público estática vacío principal()final String[] args) {} Car coche = nuevo Car(); coche.aceptar()nuevo CarElementPrintVisitor()); coche.aceptar()nuevo CarElementDoVisitor()); }}
Salida
Rueda delantera izquierda visitada Rueda delantera derecha visitando Visitar la rueda izquierda trasera Vuelta al volante derecho Cuerpo visitado Motor de visita Visiting car Kicking mi rueda delantera izquierda Kicking my front right wheel Kicking my back left wheel Kicking my back right wheel Moving my body Empezando mi motor Comenzando mi auto
Ejemplo de Lisp común
Fuentes
()defclass auto () ()elementos :initarg :elementos))()defclass autoparte () ()Nombre :initarg :nombre :initform "Nombrado-car-partamento"))()defmethod objeto de impresión ()p autoparte) streaming) ()objeto de impresión ()valor de ranura p 'nombre) streaming)()defclass rueda ()autoparte) ())()defclass cuerpo ()autoparte) ())()defclass motor ()autoparte) ())()defgeneric transversal ()función objeto otro objeto)()defmethod transversal ()función ()a auto) otro objeto) ()con lotes ()elementos) a ()lista ()e elementos) ()funcall función e otro objeto)));; hacer algunas visitas; Atrapar todos()defmethod algo ()objeto otro objeto) ()formato t "No sé cómo deben interactuar" objeto otro objeto); visitación que implica rueda e entero()defmethod algo ()objeto rueda) ()otro objeto entero) ()formato t "Tiempo de la rueda" objeto otro objeto); Visitación que implica rueda y símbolo()defmethod algo ()objeto rueda) ()otro objeto símbolo) ()formato t "La rueda del juego es simbólicamente usando el símbolo ~ s~%" objeto otro objeto)()defmethod algo ()objeto motor) ()otro objeto entero) ()formato t "estrellando motores ~s veces ~%" objeto otro objeto)()defmethod algo ()objeto motor) ()otro objeto símbolo) ()formato t "estrellante motor ~s simbólicamente usando símbolo ~s~%" objeto otro objeto)()Deja ()a ()make-instance 'auto :elementos `(),()make-instance 'wheel :nombre "front-left-wheel") ,()make-instance 'wheel :nombre "front-right-wheel") ,()make-instance 'wheel :nombre "rear-left-wheel") ,()make-instance 'wheel :nombre "rear-right-wheel") ,()make-instance 'body :nombre "cuerpo") ,()make-instance 'engine :nombre "engine")))) ; atravesar elementos de impresión ;; stream *standard-output* juega el papel de otro objeto aquí ()transversal # 'impresión a *standard-output*) ()terpri) ; imprimir nueva línea ; Traverse con contexto arbitrario de otro objeto ()transversal # 'algo a 42) ; Traverse con contexto arbitrario de otro objeto ()transversal # 'algo a 'abc)
Salida
"front-left-wheel" "front-right-wheel" "rear-left-wheel" "rear-right-wheel" "cuerpo" "engine" rueda de patada "front-left-wheel" 42 veces rueda de patada "front-right-wheel" 42 veces rueda de patadas "rear-left-wheel" 42 veces rueda de patadas "rear-derecha-wheel" 42 veces No sé cómo debe interactuar el "cuerpo" y 42 motor inicial "motor" 42 veces rueda de patada "front-left-wheel" simbólicamente utilizando el símbolo ABC rueda de patada "front-right-wheel" simbólicamente usando el símbolo ABC rueda de patada "rear-left-wheel" simbólicamente utilizando el símbolo ABC rueda de patada "rear-derecha-wheel" simbólicamente utilizando el símbolo ABC No sé cómo "cuerpo" y ABC deben interactuar motor inicial "motor" simbólicamente utilizando el símbolo ABC
Notas
El parámetro other-object
es superfluo en traverse
. La razón es que es posible usar una función anónima que llama al método de destino deseado con un objeto capturado léxicamente:
()defmethod transversal ()función ()a auto) ; otro objeto eliminado ()con lotes ()elementos) a ()lista ()e elementos) ()funcall función e))) ; desde aquí también ;; ; manera alternativa de imprimir-traverso ()transversal ()lambda ()o) ()impresión o *standard-output*) a) ; manera alternativa de hacer algo con ; elementos de a e entero 42 ()transversal ()lambda ()o) ()algo o 42) a)
Ahora, el envío múltiple ocurre en la llamada emitida desde el cuerpo de la función anónima, por lo que traverse
es solo una función de mapeo que distribuye una aplicación de función sobre los elementos de un objeto. Así desaparecen todos los rastros del Patrón Visitante, excepto la función de mapeo, en la que no hay evidencia de que dos objetos estén involucrados. Todo el conocimiento de que hay dos objetos y un despacho de sus tipos está en la función lambda.
Ejemplo de Python
Python no admite la sobrecarga de métodos en el sentido clásico (comportamiento polimórfico según el tipo de parámetros pasados), por lo que la "visita" los métodos para los diferentes tipos de modelos deben tener nombres diferentes.
Fuentes
"Ejemplo de patrón visitante."desde abc importación ABCMeta, abstractoNOT_IMPLEMENTE = "Deberías implementar esto."clase CarElement: __metaclass__ = ABCMeta @abstractmethod def aceptar()auto, visitante): aumento No Implemented Error()NOT_IMPLEMENTE)clase Cuerpo()CarElement): def aceptar()auto, visitante): visitante.visitaBody()auto)clase Motor()CarElement): def aceptar()auto, visitante): visitante.visitaEngine()auto)clase Rueda()CarElement): def __init_()auto, Nombre): auto.Nombre = Nombre def aceptar()auto, visitante): visitante.visita Rueda()auto)clase Car()CarElement): def __init_()auto): auto.elementos = [ Rueda()"a la izquierda"), Rueda()"a la derecha"), Rueda()"a la izquierda"), Rueda()"derecho"), Cuerpo(), Motor() ] def aceptar()auto, visitante): para elemento dentro auto.elementos: elemento.aceptar()visitante) visitante.visite()auto)clase CarElementVisitor: __metaclass__ = ABCMeta @abstractmethod def visitaBody()auto, elemento): aumento No Implemented Error()NOT_IMPLEMENTE) @abstractmethod def visitaEngine()auto, elemento): aumento No Implemented Error()NOT_IMPLEMENTE) @abstractmethod def visita Rueda()auto, elemento): aumento No Implemented Error()NOT_IMPLEMENTE) @abstractmethod def visite()auto, elemento): aumento No Implemented Error()NOT_IMPLEMENTE)clase CarElementDoVisitor()CarElementVisitor): def visitaBody()auto, cuerpo): impresión()"Moving my body.") def visite()auto, coche): impresión()"Iniciando mi auto.") def visita Rueda()auto, rueda): impresión()"Kicking my {} rueda.".formato()rueda.Nombre) def visitaEngine()auto, motor): impresión()"Iniciando mi motor.")clase CarElementPrintVisitor()CarElementVisitor): def visitaBody()auto, cuerpo): impresión()"El cuerpo visitante".) def visite()auto, coche): impresión()"Visitando coche.") def visita Rueda()auto, rueda): impresión()"Visitando {} rueda.".formato()rueda.Nombre) def visitaEngine()auto, motor): impresión()"Un motor visitante".)coche = Car()coche.aceptar()CarElementPrintVisitor())coche.aceptar()CarElementDoVisitor())
Salida
Visitando la rueda delantera izquierda.Visitando la rueda delantera derecha.Visitando la rueda izquierda trasera.Visitando la rueda derecha.Cuerpo visitante.Motor visitante.Un coche visitado.Kicking my front left wheel.Kicking my front right wheel.Kicking my back left wheel.Kicking my back right wheel.Mudando mi cuerpo.Empezar mi motor.Comenzando mi auto.
Abstracción
Usar Python 3 o superior permite hacer una implementación general del método accept:
clase Visitable: def aceptar()auto, visitante): Lookup = "visit_" + Tipo()auto).__qualname_.reemplazar()"., "_") retorno Getattr()visitante, Lookup)auto)
Se podría extender esto para iterar sobre el orden de resolución del método de la clase si quisiera recurrir a clases ya implementadas. También podrían usar la función de gancho de subclase para definir la búsqueda por adelantado.
Patrones de diseño relacionados
- Patrón Iterador – define un principio traversal como el patrón de visitante, sin hacer una diferenciación tipo dentro de los objetos transitados
- Codificación de la iglesia – un concepto relacionado de programación funcional, en el que se pueden modelar tipos de unión / suma etiquetados utilizando los comportamientos de los "visidores" en tales tipos, y que permite al patrón del visitante emular variantes y patrones.
Contenido relacionado
KAB-500L
Tenencia del motor Ducati
Counter-Strike (videojuego)