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 las estructuras de objetos existentes sin modificarlas. Es una forma de seguir el principio abierto/cerrado en la programación orientada a objetos y la 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 instancia como entrada e implementa el objetivo a través de un doble envío .
Los lenguajes de programación con tipos de suma y coincidencia de patrones eliminan muchos de los beneficios del patrón de visitante, ya que la clase de visitante puede ramificarse fácilmente en el tipo de objeto y generar un error de compilación si se define un nuevo tipo de objeto que el visitante aún no maneja.
El patrón de diseño Visitor [1] es uno de los veintitrés patrones de diseño conocidos del 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.
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 de entender, mantener y cambiar". [1]
Esto permite crear nuevas operaciones independientemente de las clases de una estructura de objeto agregando nuevos objetos visitantes.
Vea también el diagrama de clases y secuencias UML a continuación.
La Banda de los Cuatro define al Visitante como:
Representa una operación que se realizará sobre elementos de una estructura de objeto. Visitor 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 conectar a las API públicas, permitiendo así a sus clientes realizar operaciones en una clase utilizando una clase "visitante" sin tener que modificar la fuente. [2]
Trasladar las operaciones a las clases de visitantes es beneficioso cuando
Sin embargo, una desventaja de este patrón es que hace que las extensiones a la jerarquía de clases sean más difíciles, ya que las clases nuevas generalmente requieren visit
que se agregue un nuevo método a cada visitante.
Consideremos el diseño de un sistema de diseño asistido por computadora (CAD) en 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 se encuentra el dibujo, que es simplemente una lista de capas, más 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 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 satura 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. Una función de guardado de este tipo 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 circular en un formato raster requiere un código muy similar sin importar qué forma raster específica se use, 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 recorre 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 pasar por alto una forma en uno o más guardadores, 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 genera problemas de extensión y mantenimiento del código. A medida que aumentan las versiones del 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, save(image_tree)) en toda la jerarquía en una clase (es decir, Saver) que implementa los métodos comunes para recorrer el árbol y describe los métodos auxiliares virtuales (es decir, save_circle, save_square, etc.) que se implementarán para comportamientos específicos del formato. En el caso del ejemplo de CAD, dichos comportamientos específicos del formato se implementarían mediante una subclase de Visitor (es decir, SaverPNG). De esta manera, se elimina toda duplicación de comprobaciones de tipo y pasos de recorrido. Además, el compilador ahora se queja si se omite una forma, ya que ahora la espera la función de recorrido/guardado base común.
El patrón de visitante puede usarse para iterar sobre estructuras de datos similares a contenedores, 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 del patrón de bucle más convencional . Esto permitiría derivar información útil variada del contenido de directorios implementando una funcionalidad de visitante para cada elemento mientras se reutiliza 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, recorriendo dos contenedores al mismo tiempo con una sola i
variable). [3] : 289 Esto último requeriría escribir una funcionalidad adicional para un visitante para admitir estas características. [3] : 289
En el diagrama de clase UML anterior, la clase no implementa una nueva operación directamente. En cambio, implementa una operación de envío que "envía" (delega) una solicitud al "objeto visitante aceptado" ( ). La clase implementa la operación ( ). luego 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 recorre los elementos de una estructura de objeto ( ) y llama a cada elemento.
Primero, llama a , que llama al objeto aceptado. El elemento en sí ( ) se pasa a para que pueda "visitar" (llamar a ).
A continuación, llama a , que llama a que "visita" (llama a ).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 un solo envío , como lo hacen los lenguajes orientados a objetos comunes (como C++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python y C# ). En 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 visitante e implementan estos visit
métodos, cada uno de los cuales implementa parte del algoritmo que opera sobre la estructura del objeto. El estado del algoritmo se mantiene localmente por la clase visitante concreta.
El elemento declara un accept
método para aceptar un visitante, tomando al visitante como argumento. Los elementos concretos , derivados de la clase del 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 iteran sobre ellos, llamando accept
al método de cada uno de ellos.
El cliente crea la estructura del objeto, directa o indirectamente, e instancia los visitantes concretos. Cuando se debe realizar una operación que se implementa utilizando el patrón Visitor, se llama al accept
método del elemento o elementos de nivel superior.
Cuando se llama al accept
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 visit
se llama al método asociado, su implementación se elige en función tanto del tipo dinámico del visitante como del tipo estático del elemento, tal como se conoce desde dentro de la implementación del 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 del elemento dado, entonces el compilador detectará el error).
Por lo 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 de manera efectiva el doble envío . 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 visitante se simplifica en gran medida (también conocido como Visitante dinámico) al permitir el uso de una sobrecarga de funciones simple para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que funcione 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 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 en función de los tipos dinámicos tanto de los elementos como de los visitantes.
En este ejemplo se declara una clase independiente ExpressionPrintingVisitor
que se encarga de la impresión. Si se desea introducir 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 Addition) permanecerán sin cambios.
usando Sistema ; espacio de nombres Wikipedia ; interfaz pública Visitante { void Visita ( Literal literal ); void Visita ( Adición adición ); } clase pública ExpressionPrintingVisitor : Visitante { pública void Visita ( Literal literal ) { Console . WriteLine ( literal . Value ); } public void Visita ( Adición adición ) { double valorIzquierda = adición . Izquierda . ObtenerValor (); double valorDerecha = adición . Derecha . ObtenerValor (); var suma = adición . ObtenerValor (); Console . WriteLine ( "{0} + {1} = {2}" , valorIzquierda , valorDerecha , suma ); } } clase abstracta pública Expresión { void abstracto público Aceptar ( Visitante v ); doble abstracto público ObtenerValor (); } clase pública Literal : Expresión { public Literal ( double value ) { this.Value = value ; } público doble Valor { obtener ; establecer ; } public override void Accept ( Visitor v ) { v . Visit ( this ); } public override double GetValue () { return Value ; } } clase pública Adición : Expresión { pública Adición ( Expresión izquierda , Expresión derecha ) { Izquierda = izquierda ; Derecha = derecha ; } Expresión pública Izquierda { obtener ; establecer ; } Expresión pública Derecha { obtener ; establecer ; } public override void Accept ( Visitante v ) { Izquierda.Aceptar ( v ) ; Derecha.Aceptar ( v ) ; v.Visitar ( este ) ; } public override double GetValue ( ) { return Izquierda.ObtenerValor ( ) + Derecha.ObtenerValor ( ) ; } } clase pública estática Programa { 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 printingVisitor = new ExpressionPrintingVisitor ( ); e.Accept ( printingVisitor ) ; Console.ReadKey ( ) ; } }
En este caso, es responsabilidad del objeto saber cómo imprimirse en un flujo. El visitante aquí es entonces el objeto, no el flujo.
"No existe ninguna sintaxis para crear una clase. Las clases se crean enviando mensajes a otras clases". WriteStream subclase: #ExpressionPrinter instanceVariableNames: '' 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 . ^ anObject .Subclase de objeto : #Expresión instanceVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' .Subclase de expresión : #Literal instanceVariableNames: 'valor' classVariableNames: '' paquete: 'Wikipedia' .Clase literal >>con: aValue "Método de clase para construir una instancia de la clase Literal" ^ self new value: aValue ; usted mismo .Literal >>valor: aValor "Establecedor para valor" valor := aValor .Literal >>putOn: aStream "Un objeto Literal sabe cómo imprimirse a sí mismo" aStream nextPutAll: value asString .Subclase de expresión : #Adición instanceVariableNames: 'left right' classVariableNames: '' 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 izquierda: a ; derecha: b ; usted mismo .Adición >>izquierda: anExpression "Establecedor para la izquierda" izquierda := anExpression .Adición >>right: anExpression "Establecedor para right" right := anExpression .Adición >>putOn: aStream "Un objeto Adición sabe cómo imprimirse a sí mismo" aStream nextPut: $( . left putOn: aStream . aStream nextPut: $+ . right putOn: aStream . aStream nextPut: $) .Subclase de objeto : #Programa instanceVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' .Programa >> principal | expresión stream | expresión := Adición izquierda: ( Adición izquierda: ( Literal con: 1 ) derecha: ( Literal con: 2 )) derecha: ( Literal con: 3 ) . stream := ExpressionPrinter on: ( String new: 100 ) . stream write: expresión . Transcripción show: contenido del stream . Transcripción flush .
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 { visitWheel ( rueda Rueda ) cadena visitEngine ( motor Motor ) cadena visitBody ( cuerpo Cuerpo ) cadena visitCar ( 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 visitante ( CarElementPrintVisitor
) realiza la acción de impresión requerida. Debido a que las diferentes subclases de nodo requieren acciones ligeramente diferentes para imprimir correctamente, CarElementPrintVisitor
envía acciones basadas en la clase del argumento pasado a su visit
método. CarElementDoVisitor
, que es análogo a una operación de guardado para un formato de archivo diferente, hace lo mismo.
importar java.util.List ; interfaz CarElement { void accept ( CarElementVisitor visitante ); } interfaz CarElementVisitor { void visita ( Carrocería carrocería ); void visita ( Coche coche ); void visita ( Motor motor ); void visita ( Rueda rueda ); } la clase Wheel implementa CarElement { private final String nombre ; public Wheel ( final String nombre ) { this . nombre = nombre ; } public String getName () { devolver nombre ; } @Override public void accept ( CarElementVisitor visitor ) { /* * accept(CarElementVisitor) en Wheel implementa * accept(CarElementVisitor) en CarElement, por lo que la llamada * a accept está vinculada en tiempo de ejecución. Esto puede considerarse * el *primer* envío. Sin embargo, la decisión de llamar * visit(Wheel) (en lugar de visit(Engine) etc.) puede * tomarse durante el tiempo de compilación ya que se sabe * que 'this' es un Wheel en el momento de la compilación. Además, cada implementación de * CarElementVisitor implementa visit(Wheel), que es * otra decisión que se toma en tiempo de ejecución. Esto puede * considerarse el *segundo* envío. */ visitor . visit ( this ); } } clase Body implementa CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } } clase Engine implementa CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } } clase Car implementa CarElement { private final List < CarElement > elementos ; public Car () { this.elements = List.of ( new Rueda ( "delantera izquierda" ) , new Rueda ( "delantera derecha" ), new Rueda ( " trasera izquierda" ) , new Rueda ( "trasera derecha" ), new Carrocería ( ), new Motor () ); } @Override public void accept ( CarElementVisitor visitante ) { for ( CarElement elemento : elementos ) { elemento . accept ( visitante ); } visitante . visit ( este ); } } clase CarElementDoVisitor implementa CarElementVisitor { @Override public void visit ( Body body ) { System . println ( "Moviendo mi cuerpo " ) ; } @Override public void visit ( Car car ) { System . println ( " Arrancando mi auto" ) ; } @Override public void visit ( Rueda rueda ) { System.out.println ( " Pateando mi " + rueda.getName ( ) + " rueda " ) ; } @Override public void visit ( Motor engine ) { System.out.println ( " Arrancando mi motor " ) ; } } clase CarElementPrintVisitor implementa CarElementVisitor { @Override public void visit ( Body body ) { System . println ( "Cuerpo visitante " ) ; } @Override public void visit ( Car car ) { System.out.println ( " Coche de visita " ) ; } @Override public void visit ( Engine engine ) { System . println ( " Motor de visita" ) ; } @Override public void visit ( Rueda rueda ) { System.out.println ( " Visitando " + rueda.getName ( ) + " rueda " ) ; } } clase pública VisitorDemo { public static void main ( final String [] args ) { Auto auto = nuevo Auto (); coche.aceptar ( nuevo CarElementPrintVisitor ()) ; coche.aceptar ( nuevo CarElementDoVisitor ( ) ) ; } }
Visita rueda delantera izquierdaVisitando la rueda delantera derechaVisitando la rueda trasera izquierdaVisitando la rueda trasera derechaOrganismo visitanteMotor de visitaCoche de visitaPateando mi rueda delantera izquierdaPateando mi rueda delantera derechaPateando mi rueda trasera izquierdaPateando mi rueda trasera derechaMoviendo mi cuerpoArrancando mi motorArrancando mi coche
( defclass auto () (( elementos :initarg :elementos ))) ( defclass auto-part () (( nombre :initarg :name :initform "<parte-de-automóvil-sin-nombre>" ))) ( defmethod print-object (( p auto-part ) stream ) ( print-object ( slot-value p 'name ) stream )) ( defclass rueda ( auto-parte ) ()) ( defclass cuerpo ( auto-parte ) ()) ( defclass motor ( auto-parte ) ()) ( defgeneric traverse ( función objeto otro-objeto )) ( defmethod traverse ( function ( a auto ) otro-objeto ) ( with-slots ( elementos ) a ( dolist ( e elementos ) ( funcall function e otro-objeto )))) ;; visitas para hacer algo;; catch all ( defmethod do-something ( object otro-objeto ) ( format t "no sé cómo deberían interactuar ~s y ~s~%" object otro-objeto )) ;; visita que involucra rueda y entero ( defmethod do-something (( object wheel ) ( other-object whole )) ( format t "pateando rueda ~s ~s veces~%" object other-object )) ;; visita que involucra rueda y símbolo ( defmethod do-something (( object wheel ) ( other-object symbol )) ( format t "pateando la rueda ~s simbólicamente usando el símbolo ~s~%" object other-object )) ( defmethod do-something (( object engine ) ( other-object entero )) ( formato t "motor de arranque ~s ~s veces~%" object other-object )) ( defmethod do-something (( object engine ) ( other-object symbol )) ( format t "iniciando el motor ~s simbólicamente usando el símbolo ~s~%" object other-object )) ( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'wheel :name "rueda-delantera-izquierda" ) , ( make-instance 'wheel :name "rueda-delantera-derecha" ) , ( make-instance 'wheel :name "rueda-trasera-izquierda" ) , ( make-instance 'wheel :name "rueda-trasera-derecha" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; atravesar para imprimir elementos ;; stream *standard-output* juega el rol de otro-objeto aquí ( atravesar #' imprimir a *standard-output* ) ( terpri ) ;; imprimir nueva línea ;; recorrer con contexto arbitrario desde otro objeto ( traverse #' hacer-algo a 42 ) ;; recorrer con contexto arbitrario desde otro objeto ( recorrer # ' hacer-algo a 'abc ))
"rueda delantera izquierda""rueda delantera derecha""rueda trasera izquierda""rueda trasera derecha""cuerpo""motor"pateando la rueda "rueda delantera izquierda" 42 vecespateando la rueda "delantera derecha" 42 vecespateando el volante "rueda trasera izquierda" 42 vecespateando la rueda "trasera derecha" 42 vecesNo sé cómo deberían interactuar "cuerpo" y 42Arrancar el motor "motor" 42 vecespateando la rueda "rueda delantera izquierda" simbólicamente usando el símbolo ABCpateando la rueda "rueda delantera derecha" simbólicamente usando el símbolo ABCpateando la rueda "rueda trasera izquierda" simbólicamente usando el símbolo ABCpateando la rueda "rueda trasera derecha" simbólicamente usando el símbolo ABCNo sé cómo deberían interactuar "cuerpo" y ABCArrancar el motor "motor" simbólicamente utilizando 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 traverse ( function ( a auto )) ;; otro-objeto eliminado ( with-slots ( elementos ) a ( dolist ( e elementos ) ( funcall function 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 ( recorrer ( lambda ( o ) ( hacer algo o 42 )) a )
Ahora bien, el envío múltiple se produce en la llamada emitida desde el cuerpo de la función anónima, por lo que traverse
es simplemente una función de mapeo que distribuye una aplicación de función sobre los elementos de un objeto. De este modo, desaparecen todos los rastros del patrón Visitor, excepto en el caso de la función de mapeo, en la que no hay evidencia de que estén involucrados dos objetos. Todo el conocimiento de que existen dos objetos y un envío sobre sus tipos se encuentra 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. """desde abc import ABCMeta , método abstractoNOT_IMPLEMENTED = "Deberías implementar esto."clase CarElement ( metaclass = ABCMeta ): @abstractmethod def accept ( self , visitor ): generar NotImplementedError ( NOT_IMPLEMENTED )clase Body ( CarElement ) : def accept ( self , visitor ) : visitor.visitBody ( self )clase Engine ( CarElement ) : def accept ( self , visitor ) : visitor.visitEngine ( self )clase Wheel ( CarElement ) : def __init __ ( self , name ) : self.name = name def accept ( self , visitor ) : visitor.visitWheel ( self ) clase Car ( CarElement ): def __init__ ( self ) : self.elements = [ Wheel ( " delantera izquierda" ), Wheel ( "delantera derecha" ), Wheel ( "trasera izquierda" ), Wheel ( "trasera derecha" ), Body (), Engine () ] def accept ( self , visitor ) : for elemento in self.elements : elemento.accept ( visitor ) visitante.visitCar ( self ) clase CarElementVisitor ( metaclass = ABCMeta ): @abstractmethod def visitBody ( self , elemento ) : generar NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitEngine ( self , elemento ): generar NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitWheel ( self , elemento ): generar NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self , elemento ): generar NotImplementedError ( NOT_IMPLEMENTED )clase CarElementDoVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( "Moviendo mi cuerpo." ) def visitCar ( self , car ): print ( "Arrancando mi auto." ) def visitWheel ( self , wheel ): print ( "Pateando mi {} rueda." . format ( wheel . name )) def visitEngine ( self , engine ): print ( "Arrancando mi motor." )clase CarElementPrintVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( "Visitando cuerpo." ) def visitCar ( self , car ): print ( "Visitando auto." ) def visitWheel ( self , wheel ): print ( "Visitando {} wheel." . format ( wheel . name )) def visitEngine ( self , engine ): 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. Visitando la carrocería. Visitando el motor. Visitando el coche. Pateando mi rueda delantera izquierda. Pateando mi rueda delantera derecha. Pateando mi rueda trasera izquierda. Pateando mi rueda trasera derecha. Moviendo mi cuerpo. Encendiendo mi motor. Encendiendo mi coche.
El uso de Python 3 o superior permite realizar una implementación general del método accept:
clase Visitable : def accept ( self , visitor ): lookup = "visit_" + self .__ qualname__ . replace ( "." , "_" ) return getattr ( visitor , lookup )( self )
Se podría ampliar esto para iterar sobre el orden de resolución de métodos de la clase si se quisiera recurrir a clases ya implementadas. También se podría usar la función de gancho de subclase para definir la búsqueda con antelación.
{{cite book}}
: CS1 maint: varios nombres: lista de autores ( enlace )