Articulo de referencia

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 ...

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?

  • Debería ser posible definir una nueva operación para (algunas) clases de una estructura de objeto 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 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?

  • Define un objeto (visitante) separado que implementa una operación que se realizará en elementos de una estructura de objeto.
  • Los clientes recorren la estructura del objeto y llaman a una operación de envío accept (visitor) en un elemento, que "envía" (delega) la solicitud al "objeto visitante aceptado". El objeto visitante luego realiza la operación en el elemento ("visita el elemento").

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

  • Se requieren muchas operaciones no relacionadas en una estructura de objeto,
  • Las clases que componen la estructura del objeto son conocidas y no se espera que cambien,
  • Es necesario agregar nuevas operaciones con frecuencia,
  • un algoritmo involucra varias clases de la estructura del objeto, pero se desea administrarlo en una sola ubicación,
  • Un algoritmo debe funcionar en varias jerarquías de clases independientes.

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 ( Visitante v ) { v . Visita ( este ); } 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 = new Addition ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new 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". 
Subclase WriteStream :  #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

Diagrama UML del ejemplo del patrón Visitante con elementos de automóvil

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 izquierda
Visitando la rueda delantera derecha
Visitando la rueda trasera izquierda
Visitando la rueda trasera derecha
Organismo visitante
Motor de visita
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 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 veces
pateando la rueda "delantera derecha" 42 veces
pateando el volante "rueda trasera izquierda" 42 veces
pateando la rueda "trasera derecha" 42 veces
No sé cómo deberían interactuar "cuerpo" y 42
Arrancar el motor "motor" 42 veces
pateando la rueda "rueda delantera izquierda" simbólicamente usando el símbolo ABC
pateando la rueda "rueda delantera derecha" simbólicamente usando el símbolo ABC
pateando la rueda "rueda trasera izquierda" simbólicamente usando el símbolo ABC
pateando la rueda "rueda trasera derecha" simbólicamente usando el símbolo ABC
No sé cómo deberían interactuar "cuerpo" y ABC
Arrancar 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 abstracto

NOT_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.

  • Patrón iterador : define un principio de recorrido como el patrón de visitante, sin hacer una diferenciación de tipos dentro de los objetos recorridos
  • Codificación de la iglesia : un concepto relacionado con la programación funcional, en el que los tipos de unión/suma etiquetados pueden modelarse utilizando los comportamientos de los "visitantes" en dichos tipos, y que permite que el patrón de visitante emule variantes y patrones .

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  .
  • La familia de patrones de diseño de Visitor en Wayback Machine (archivado el 22 de octubre de 2015). Archivos adicionales: 12 de abril de 2004, 5 de marzo de 2002. Un borrador de capítulo de Los principios, patrones y prácticas del desarrollo ágil de software , Robert C. Martin , Prentice Hall
  • Patrón de visitante en UML y en LePUS3 (un lenguaje de descripción de diseño)
  • Artículo "Componentización: el ejemplo del visitante" de Bertrand Meyer y Karine Arnout, Computer (IEEE), vol. 39, núm. 7, julio de 2006, páginas 23-30.
  • Artículo Una reconstrucción teórica de tipos del patrón de visitantes
  • Artículo "La esencia del patrón Visitor" de Jens Palsberg y C. Barry Jay. Documento IEEE-CS COMPSAC de 1997 que muestra que los métodos accept() son innecesarios cuando la reflexión está disponible; introduce el término "Walkabout" para la técnica.
  • Artículo "Un momento para la reflexión" de Bruce Wallace, subtitulado "Las capacidades de reflexión de Java 1.2 eliminan los engorrosos métodos accept() de su patrón Visitor"
  • Patrón de visitante que utiliza reflexión (java).
  • Proyecto de código abierto PerfectJPattern, proporciona una implementación libre de contexto y segura de tipos del patrón Visitor en Java basado en Delegados.
  • Patrón de diseño para visitantes
Obtenido de "https://es.wikipedia.org/w/index.php?title=Patrón_de_visitantes&oldid=1236030227"