stringtranslate.com

Patrón de visitantes

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.

Descripción general

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.

¿Qué problemas puede resolver el patrón de diseño Visitor?

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]

¿Qué solución describe el patrón de diseño Visitor?

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.

Definició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]

Ventajas

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 visitque se agregue un nuevo método a cada visitante.

Solicitud

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.

Bucles de iteració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 ivariable). [3] : 289  Esto último requeriría escribir una funcionalidad adicional para un visitante para admitir estas características. [3] : 289 

Estructura

Diagrama de clases y secuencias UML

Un ejemplo de diagrama de clases UML y diagrama de secuencia para el patrón de diseño Visitor. [4]

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 ( ).ElementAElementA accept(visitor)visitor.visitElementA(this)Visitor1visitElementA(e:ElementA)
ElementBaccept(visitor)visitor.visitElementB(this)Visitor1visitElementB(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 ).ClientElementA,ElementBaccept(visitor)
Clientaccept(visitor)ElementAvisitElementA(this)visitorthisvisitorElementAoperationA()
Clientaccept(visitor)ElementBvisitElementB(this)visitorElementBoperationB()

Diagrama de clases

Visitante en Lenguaje de Modelado Unificado (UML). [5] : 381 
Visitante en LePUS3 (leyenda)

Detalles

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 visitmétodo que toma el elemento como argumento para cada clase de elemento. Los visitantes concretos se derivan de la clase visitante e implementan estos visitmé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 acceptmétodo para aceptar un visitante, tomando al visitante como argumento. Los elementos concretos , derivados de la clase del elemento, implementan el acceptmétodo. En su forma más simple, esto no es más que una llamada al visitmétodo del visitante. Los elementos compuestos , que mantienen una lista de objetos secundarios, normalmente iteran sobre ellos, llamando acceptal 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 acceptmétodo del elemento o elementos de nivel superior.

Cuando se llama al acceptmé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 visitse 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 acceptmé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 visitmé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.

Ejemplo de C#

En este ejemplo se declara una clase independiente ExpressionPrintingVisitorque 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 ( ) ; } }       

Ejemplo de Smalltalk

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 .  


Ir

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 }           

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 printmé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, CarElementPrintVisitorenvía acciones basadas en la clase del argumento pasado a su visitmétodo. CarElementDoVisitor, que es análogo a una operación de guardado para un formato de archivo diferente, hace lo mismo.

Diagrama

Fuentes

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 ( ) ) ; } }    


Producción

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

Ejemplo de Common Lisp

Fuentes

( 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 ))    

Producción

"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

Notas

El other-objectpará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 traversees 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.

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 los métodos de "visita" para los diferentes tipos de modelos deben tener nombres diferentes.

Fuentes

""" 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 ( ) )

Producción

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.

Abstracción

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.

Patrones de diseño relacionados

Véase también

Referencias

  1. ^ de Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides (1994). Patrones de diseño: elementos de software reutilizable orientado a objetos . Addison Wesley. pp. 331 y siguientes. ISBN 0-201-63361-2.{{cite book}}: CS1 maint: varios nombres: lista de autores ( enlace )
  2. ^ Ejemplo del mundo real de patrón de visitantes
  3. ^ abcd Budd, Timothy (1997). Introducción a la programación orientada a objetos (2.ª ed.). Reading, Mass.: Addison-Wesley. ISBN 0-201-82419-1.OCLC 34788238  .
  4. ^ "El patrón de diseño Visitor - Estructura y colaboración". w3sDesign.com . Consultado el 12 de agosto de 2017 .
  5. ^ Reddy, Martin (2011). Diseño de API para C++. Boston: Morgan Kaufmann. ISBN 978-0-12-385004-1.OCLC 704559821  .

Enlaces externos