Un patrón de visitante es un patrón de diseño de software que separa el algoritmo de la estructura del objeto . Debido a esta separación, se pueden agregar nuevas operaciones a estructuras de objetos existentes sin modificar las estructuras. Es una forma de seguir el principio abierto/cerrado en programación orientada a objetos e ingeniería de software .
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 mediante doble envío .
Los lenguajes de programación con tipos de suma y coincidencia de patrones obvian muchos de los beneficios del patrón de visitante, ya que la clase de visitante puede bifurcarse fácilmente en el tipo de objeto y generar un error de compilación si se define un nuevo tipo de objeto, lo cual hace el visitante. Aún no lo manejo.
El patrón de diseño Visitante [1] es uno de los veintitrés patrones de diseño conocidos de la Banda de los Cuatro 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.
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 distintas clases de nodos conduce a un sistema que es difícil comprender, mantener y cambiar". [1]
Esto hace posible crear nuevas operaciones independientemente de las clases de una estructura de objetos agregando nuevos objetos visitantes.
Consulte también el diagrama de secuencia y clase UML a continuación.
La Banda de los Cuatro define al Visitante como:
Representar una operación que se realizará en elementos de una estructura de objeto. Visitor le permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.
La naturaleza del Visitante lo convierte en un patrón ideal para conectarse a API públicas, permitiendo así a sus clientes realizar operaciones en una clase utilizando una clase "visitante" sin tener que modificar la fuente. [2]
Mover las operaciones a clases de visitantes es beneficioso cuando
Sin embargo, una desventaja de este patrón es que dificulta las extensiones de la jerarquía de clases, ya que las nuevas clases normalmente requieren visit
que se agregue un nuevo método a cada visitante.
Considere el diseño de un sistema de diseño asistido por computadora (CAD) 2D. En esencia, existen varios tipos para representar formas geométricas básicas como círculos, líneas y arcos. Las entidades están ordenadas en capas y en la parte superior de la jerarquía de tipos está el dibujo, que es simplemente una lista de capas, además de algunas propiedades agregadas.
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 de la jerarquía. Pero también resulta útil poder guardar dibujos en otros formatos de archivo. Agregar cada vez más métodos para guardar en muchos formatos de archivos diferentes pronto satura la estructura de datos geométrica original relativamente pura.
Una forma ingenua de resolver esto sería mantener funciones separadas para cada formato de archivo. Esta función de guardar tomaría un dibujo como entrada, lo recorrería y lo 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 formato ráster requiere un código muy similar sin importar qué forma ráster 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 externo que atraviesa los objetos, con un gran árbol de decisión dentro del bucle que consulta el tipo de objeto. Otro problema con este enfoque es que es muy fácil pasar por alto 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 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 la operación lógica (es decir, guardar (image_tree)) en toda la jerarquía en una clase (es decir, Saver) que implementa los métodos comunes para atravesar el árbol y describe los métodos auxiliares virtuales (es decir, save_circle, save_square, etc.) que se implementarán para formatear comportamientos específicos. En el caso del ejemplo de CAD, dichos comportamientos específicos del formato serían implementados por una subclase de Visitante (es decir, SaverPNG). Como tal, se elimina toda duplicación de comprobaciones de tipo y pasos transversales. Además, el compilador ahora se queja si se omite una forma, ya que ahora la función común de recorrido/guardado de base la espera.
El patrón de visitante se puede utilizar para la iteración sobre estructuras de datos tipo contenedor , al igual que el patrón Iterador, pero con una funcionalidad limitada. [3] : 288 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 obtener información útil del contenido de los directorios mediante la implementación de una funcionalidad de visitante para cada elemento y al mismo tiempo reutilizar el código de iteración. Se emplea ampliamente en sistemas Smalltalk y también se puede encontrar en C++. [3] : 289 Sin embargo, una desventaja de este enfoque es que no se puede salir del bucle fácilmente o iterar simultáneamente (en paralelo, es decir, atravesando dos contenedores al mismo tiempo mediante una sola i
variable). [3] : 289 Esto último requeriría escribir funciones adicionales para que un visitante admita estas características. [3] : 289
En el diagrama de clases UML anterior, la clase no implementa una nueva operación directamente. En su lugar, implementa una operación de envío que "envía" (delega) una solicitud al "objeto visitante aceptado" ( ). La clase implementa la operación ( ). luego se implementa enviando a . La clase implementa la operación ( ).ElementA
ElementA
accept(visitor)
visitor.visitElementA(this)
Visitor1
visitElementA(e:ElementA)
ElementB
accept(visitor)
visitor.visitElementB(this)
Visitor1
visitElementB(e:ElementB)
El diagrama de secuencia UML
muestra las interacciones en tiempo de ejecución: el objeto atraviesa los elementos de una estructura de objeto ( ) y llama a cada elemento.
Primero, las llamadas a , que llama al objeto aceptado. El elemento en sí ( ) se pasa a para que pueda "visitar" (llamar ).
A partir de entonces, el llama a , que llama a quien "visita" (llama ).Client
ElementA,ElementB
accept(visitor)
Client
accept(visitor)
ElementA
visitElementA(this)
visitor
this
visitor
ElementA
operationA()
Client
accept(visitor)
ElementB
visitElementB(this)
visitor
ElementB
operationB()
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 visitante .
El visitante declara un visit
método, que toma el elemento como argumento, para cada clase de elemento. Los visitantes concretos se derivan de la clase de visitante e implementan estos visit
métodos, cada uno de los cuales implementa parte del algoritmo que opera en la estructura del objeto. El estado del algoritmo lo mantiene localmente la clase de visitante concreta.
El elemento declara un accept
método para aceptar un visitante, tomándolo como argumento. Los elementos concretos , derivados de la clase de elemento, implementan el accept
método. En su forma más simple, esto no es más que una llamada al visit
método del visitante. Los elementos compuestos , que mantienen una lista de objetos secundarios, normalmente los iteran y llaman al accept
método de cada niño.
El cliente crea la estructura del objeto, directa o indirectamente, y crea instancias de los visitantes concretos. Cuando se va a realizar una operación que se implementa utilizando el patrón Visitante, llama al accept
método de los elementos de nivel superior.
Cuando accept
se llama al método 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 asociado visit
, su implementación se elige en función tanto del tipo dinámico del visitante como del tipo estático del elemento, como se conoce desde la implementación del accept
método, que es lo mismo que el tipo dinámico del elemento. (Como beneficio adicional, si el visitante no puede manejar un argumento del tipo de elemento dado, entonces el compilador detectará el error).
Por tanto, la implementación del visit
método 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ío múltiple, no solo envío único, 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 una sobrecarga de funciones simple para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que opere únicamente 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 Visitante 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 según los tipos dinámicos tanto de los elementos como de los elementos. visitantes.
Este ejemplo declara una ExpressionPrintingVisitor
clase separada que se encarga de la impresión. Si se desea la introducción de un nuevo visitante concreto, se creará una nueva clase para implementar la interfaz Visitor y se proporcionarán nuevas implementaciones para los métodos Visit. Las clases existentes (Literal y Suma) permanecerán sin cambios.
usando Sistema ; espacio de nombres Wikipedia ; visitante de interfaz pública { visita vacía ( literal literal ); visita nula ( adición adicional ); } clase pública ExpressionPrintingVisitor : Visitante { visita pública vacía ( literal literal ) { Consola . WriteLine ( literal . Valor ); } visita pública vacía ( adición adicional ) { doble valor izquierdo = adición . Izquierda . ObtenerValor (); doble rightValue = suma . Bien . ObtenerValor (); var suma = suma . ObtenerValor (); Consola . WriteLine ( "{0} + {1} = {2}" , valorizquierdo , valorderecho , suma ); } } Expresión de clase abstracta pública { vacío abstracto público Aceptar ( Visitante v ); resumen público doble GetValue (); } Literal de clase pública : Expresión { Literal público ( valor doble ) { this . Valor = valor ; } Valor doble público { obtener ; colocar ; } anulación pública nula Aceptar ( Visitante v ) { v . Visita ( este ); } anulación pública doble GetValue () { Valor de retorno ; } } Adición de clase pública : Expresión { Adición pública ( Expresión izquierda , Expresión derecha ) { Izquierda = izquierda ; Derecha = derecha ; } Expresión pública izquierda { get ; colocar ; } Expresión pública correcta { get ; colocar ; } anulación pública nula Aceptar ( Visitante v ) { Izquierda . Aceptar ( v ); Bien . Aceptar ( v ); v . Visita ( este ); } anulación pública doble GetValue () { retorno Izquierda . ObtenerValor () + Derecha . ObtenerValor (); } } programa de clase estática pública { public static void Main ( string [] args ) { // Emular 1 + 2 + 3 var e = nueva adición ( nueva adición ( nuevo literal ( 1 ), nuevo literal ( 2 ) ), nuevo literal ( 3) ) ); var printVisitor = new ExpressionPrintingVisitor (); mi . Aceptar ( impresiónVisitante ); Consola . Leer la clave (); } }
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 existe una sintaxis para crear una clase. Las clases se crean enviando mensajes a otras clases". Subclase WriteStream : #ExpressionPrinter instanciaVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' .ExpressionPrinter >>write: anObject "Delega la acción al objeto. El objeto no necesita ser de ninguna clase especial; solo necesita poder entender el mensaje #putOn:" anObject putOn: self . ^ un objeto . Subclase de objeto : #Expresión instanciaVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' . Subclase de expresión : #Literal instanciaVariableNames: 'valor' claseVariableNames: '' paquete: 'Wikipedia' .Clase literal >>con: aValue "Método de clase para construir una instancia de la clase Literal" ^ valor nuevo propio : aValue ; tú mismo . Literal >>valor: aValue "Establecedor de valor" valor : = aValue .Literal >>putOn: aStream "Un objeto Literal sabe cómo imprimirse" aStream nextPutAll: valor asString . Subclase de expresión : #Addition instanciaVariableNames: 'izquierda derecha' claseVariableNames: '' paquete: 'Wikipedia' .Clase de adición >>izquierda: a derecha: b "Método de clase para construir una instancia de la clase de adición" ^ self new left: a ; derecha: b ; tú mismo .Adición >>izquierda: unaExpresión "Establecedor de izquierda" izquierda := unaExpresión .Adición >>derecha: unaExpresión "Establecedor de la derecha" derecha : = unaExpresión .Suma >>putOn: aStream "Un objeto Suma sabe cómo imprimirse" aStream nextPut: $( . left putOn: aStream . aStream nextPut: $+ . right putOn: aStream . aStream nextPut: $) . Subclase de objeto : #Programa instanciaVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' .Programa >> principal | flujo de expresión | expresión := Suma izquierda: ( Suma izquierda: ( Literal con: 1 ) derecha: ( Literal con: 2 )) derecha: ( Literal con: 3 ) . flujo : = ExpressionPrinter en: ( Cadena nueva: 100 ) . escritura de flujo : expresión . "Programa de transcripción : contenido de la transmisión ". Transcripción al ras .
Go no admite la sobrecarga de métodos, por lo que los métodos de visita necesitan nombres diferentes. Una interfaz de visitante típica podría ser
tipo Interfaz de visitante { visitaRueda ( rueda Rueda ) cadena visitaMotor ( motor Motor ) cadena visitaCuerpo ( cuerpo Cuerpo ) cadena visitaCoche ( coche Coche ) cadena }
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 print
métodos para cada subclase de nodo ( Wheel
, Engine
, Body
y Car
), una clase de visitante ( CarElementPrintVisitor
) realiza la acción de impresión requerida. Debido a que diferentes subclases de nodos requieren acciones ligeramente diferentes para imprimirse correctamente, CarElementPrintVisitor
distribuye acciones según la clase del argumento pasado a su visit
método. CarElementDoVisitor
, que es análoga a una operación de guardar para un formato de archivo diferente, hace lo mismo.
importar java.util.List ; interfaz CarElement { vacío aceptar ( visitante CarElementVisitor ); } interfaz CarElementVisitor { visita nula ( Cuerpo cuerpo ) ; visita nula ( Car car ); visita nula ( Motor motor ); visita nula ( Rueda rueda ); } class Wheel implementa CarElement { nombre de cadena final privada ; Rueda pública ( nombre de cadena final ) { this . nombre = nombre ; } cadena pública getName () { nombre de retorno ; } @Override public void aceptar ( CarElementVisitor visitante ) { /* * aceptar(CarElementVisitor) en Wheel implementa * aceptar(CarElementVisitor) en CarElement, por lo que la llamada * a aceptar está vinculada en tiempo de ejecución. Esto puede considerarse * el *primer* envío. Sin embargo, la decisión de llamar * visit(Wheel) (a diferencia de visit(Engine), etc.) se puede * tomar durante el tiempo de compilación ya que se sabe que 'esto' en el momento de la * compilación es una Rueda. Además, cada implementación de * CarElementVisitor implementa la visita (Rueda), que es * otra decisión que se toma en tiempo de ejecución. Este puede * considerarse el *segundo* envío. */ visitante . visitar ( esto ); } } class Body implementa CarElement { @Override public void aceptar ( visitante CarElementVisitor ) { visitante . visitar ( esto ); } } class Engine implementa CarElement { @Override public void aceptar ( visitante CarElementVisitor ) { visitante . visitar ( esto ); } } class Car implementa CarElement { elementos privados finales List <CarElement> ; Coche público () { este . elementos = Lista . de ( nueva rueda ( "delantera izquierda" ), nueva rueda ( "delantera derecha" ), nueva rueda ( "detrás izquierda" ), nueva rueda ( "detrás derecha" ), nueva carrocería (), nuevo motor () ); } @Override public void aceptar ( visitante CarElementVisitor ) { para ( elemento CarElement : elementos ) { elemento . aceptar ( visitante ); } visitante . visitar ( esto ); } } class CarElementDoVisitor implements CarElementVisitor { @Override visita nula pública ( Cuerpo cuerpo ) { System . afuera . println ( "Mover mi cuerpo" ); } @Override visita nula pública ( Coche coche ) { System . afuera . println ( "Arrancar mi auto" ); } @Override visita nula pública ( Rueda rueda ) { System . afuera . println ( "Pateando mi " + rueda . getName () + "rueda" ); } @Override visita nula pública ( Motor motor ) { System . afuera . println ( "Arrancando mi motor" ); } } class CarElementPrintVisitor implementa CarElementVisitor { @Override visita nula pública ( Cuerpo cuerpo ) { System . afuera . println ( "Organismo visitante" ); } @Override visita nula pública ( Coche coche ) { System . afuera . println ( "Coche de visita" ); } @Override visita nula pública ( Motor motor ) { System . afuera . println ( "Motor de visitas" ); } @Override visita nula pública ( Rueda rueda ) { System . afuera . println ( "Visitando" + rueda . getName () + "rueda" ); } } clase pública VisitorDemo { public static void main ( final String [] args ) { Coche coche = nuevo Coche (); auto . aceptar ( nuevo CarElementPrintVisitor ()); auto . aceptar ( nuevo CarElementDoVisitor ()); } }
Visita a la rueda delantera izquierdaVisitando la rueda delantera derechaVisitando la rueda trasera izquierdaVisitando la rueda trasera derechaOrganismo visitantemotor visitanteCoche de visitaPateando mi rueda delantera izquierdaPateando mi rueda delantera derechaPateando mi rueda trasera izquierdaPateando mi rueda trasera derechamoviendo mi cuerpoArrancando mi motorArrancando mi auto
( defclass auto () (( elementos :initarg :elementos ))) ( defclass auto-part () (( nombre :initarg :nombre :initform "<pieza-de-coche-sin nombre>" ))) ( defmethod objeto de impresión (( p auto-part ) flujo ) ( objeto de impresión ( valor de ranura p ' nombre ) flujo )) ( rueda defclass ( autoparte ) ()) ( cuerpo defclass ( autoparte ) ()) ( motor defclass ( autoparte ) ()) ( recorrido defgenérico ( función objeto otro-objeto )) ( recorrido defmethod ( función ( a auto ) otro objeto ) ( con ranuras ( elementos ) a ( dolist ( e elementos ) ( funcall función e otro objeto )))) ;; visitas para hacer algo;; capturar todo ( defmethod hacer algo ( objeto otro-objeto ) ( formato t "no sé cómo deben interactuar ~s y ~s~%" objeto otro-objeto )) ;; visita que involucra rueda y número entero ( defmethod hacer algo (( objeto rueda ) ( otro objeto entero )) ( formato t "rueda que patea ~s ~s veces~%" objeto otro-objeto )) ;; visita que involucra rueda y símbolo ( defmethod hacer algo (( objeto rueda ) ( símbolo de otro objeto )) ( formato t "rueda que patea ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto )) ( defmethod hacer algo (( objeto motor ) ( otro objeto entero )) ( formato t "arrancar motor ~s ~s veces~%" objeto otro-objeto )) ( defmethod hacer algo (( objeto motor ) ( símbolo de otro objeto )) ( formato t "arrancar el motor ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto )) ( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'rueda :nombre "rueda delantera izquierda" ) , ( make-instance 'rueda :nombre "rueda delantera derecha" ) , ( make-instance 'rueda :nombre "rueda-trasera-izquierda" ) , ( make-instance 'rueda :nombre "rueda-trasera-derecha" ) , ( make-instance 'cuerpo :nombre "cuerpo" ) , ( make- instancia 'motor :nombre "motor" ))))) ;; atravesar para imprimir elementos ;; la secuencia *salida-estándar* desempeña el papel de otro objeto aquí ( traverse #' imprime una *salida-estándar* ) ( terpri ) ;; imprimir nueva línea ;; atravesar con contexto arbitrario desde otro objeto ( traverse #' hacer algo a 42 ) ;; atravesar con contexto arbitrario desde otro objeto ( traverse #' hacer algo a 'abc ))
"rueda delantera izquierda""rueda delantera derecha""rueda trasera izquierda""rueda trasera derecha""cuerpo""motor"Patear la rueda "rueda delantera izquierda" 42 vecesPatear la rueda "rueda delantera derecha" 42 vecesPatear la rueda "rueda trasera izquierda" 42 veces.Patear la rueda "rueda trasera derecha" 42 veces.No sé cómo deberían interactuar "cuerpo" y 42.arrancar el motor "motor" 42 vecesPatear la rueda "rueda delantera izquierda" simbólicamente usando el símbolo ABCPateando la rueda "rueda delantera derecha" simbólicamente usando el símbolo ABCrueda de patada "rueda trasera izquierda" simbólicamente usando el símbolo ABCrueda de patada "rueda trasera derecha" simbólicamente usando el símbolo ABCNo sé cómo deberían interactuar "cuerpo" y ABC.arrancar el motor "motor" simbólicamente usando el símbolo ABC
El other-object
parámetro es superfluo en traverse
. La razón es que es posible utilizar una función anónima que llame al método de destino deseado con un objeto capturado léxicamente:
( defmethod transversal ( función ( a auto )) ;; otro objeto eliminado ( con ranuras ( elementos ) a ( dolist ( e elementos ) ( funcall función e )))) ;; desde aquí también ;; ... ;; forma alternativa de imprimir-traverse ( traverse ( lambda ( o ) ( print o *standard-output* )) a ) ;; forma alternativa de hacer algo con ;; elementos de a y entero 42 ( atravesar ( lambda ( o ) ( hacer 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í, todos los rastros del Patrón de Visitante desaparecen, 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 envío sobre sus tipos está en la función lambda.
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 los métodos de "visita" para los diferentes tipos de modelos deben tener nombres diferentes.
""" Ejemplo de patrón de visitante. """de abc import ABCMeta , método abstractoNOT_IMPLEMENTED = "Deberías implementar esto."clase CarElement ( metaclase = ABCMeta ): @abstractmethod def aceptar ( self , visitante ): generar NotImplementedError ( NOT_IMPLEMENTED ) cuerpo de clase ( CarElement ): def aceptar ( self , visitante ): visitante . visitaCuerpo ( yo )clase Motor ( CarElement ): def aceptar ( self , visitante ): visitante . visitarEngine ( yo )clase Rueda ( CarElement ): def __init__ ( self , nombre ): self . nombre = nombre def aceptar ( auto , visitante ): visitante . visitWheel ( yo )clase Coche ( CarElement ): def __init__ ( self ): self . elementos = [ Rueda ( "delantera izquierda" ), Rueda ( "delantera derecha" ), Rueda ( "detrás izquierda" ), Rueda ( "detrás derecha" ), Carrocería (), Motor () ] def aceptar ( self , visitante ): para elemento en self . elementos : elemento . aceptar ( visitante ) visitante . visitarCoche ( yo )clase CarElementVisitor ( metaclass = ABCMeta ): @abstractmethod def visitBody ( self , elemento ): levanta NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitEngine ( self , elemento ): levanta NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitWheel ( self , elemento ): levanta NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self , elemento ): generar NotImplementedError ( NOT_IMPLEMENTED )class CarElementDoVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( "Mover mi cuerpo." ) def visitCar ( self , car ): print ( "Arrancar mi auto." ) def visitWheel ( self , rueda ): print ( "Pateando mi {} rueda." formato ( rueda . nombre ) ) def visitEngine ( self , motor ): print ( "Arrancando mi motor" ).class CarElementPrintVisitor ( CarElementVisitor ): def visitBody ( self , cuerpo ): print ( "Cuerpo visitante." ) def visitCar ( self , auto ): print ( "Coche visitante." ) def visitWheel ( self , rueda ): print ( "Visitante {} rueda." . formato ( rueda . nombre )) def visitEngine ( self , motor ): print ( "Visitando motor." )coche = Coche () coche . aceptar ( CarElementPrintVisitor ()) coche . aceptar ( CarElementDoVisitor ())
Visitando la rueda delantera izquierda. Visitando la rueda delantera derecha. Visitando la rueda trasera izquierda. Visitando la rueda trasera derecha. Cuerpo visitante. Motor visitante. Coche de visita. Pateando mi rueda delantera izquierda. Pateando mi rueda delantera derecha. Pateando mi rueda trasera izquierda. Pateando mi rueda trasera derecha. Moviendo mi cuerpo. Arrancando mi motor. Arrancando mi auto.
Usar Python 3 o superior permite realizar una implementación general del método de aceptación:
clase Visitable : def aceptar ( self , visitante ): búsqueda = "visit_" + tipo ( self ) . __cualnombre__ . reemplazar ( "." , "_" ) devolver getattr ( visitante , búsqueda ) ( 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 utilizar la función de enlace de subclase para definir la búsqueda por adelantado.
{{cite book}}
: Mantenimiento CS1: varios nombres: lista de autores ( enlace )