Articulo de referencia

Patrón de visitantes

El patrón Visitor es un patrón de diseño de software que separa el algoritmo de la estructura del objeto . Gracias a esta separación, se pueden añadir nuevas operaciones a las e...

El patrón Visitor es un patrón de diseño de software que separa el algoritmo de la estructura del objeto . Gracias a esta separación, se pueden añadir nuevas operaciones a las estructuras de objetos existentes sin modificarlas. Es una forma de seguir el principio de 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 modificarlas. En su lugar, se crea una clase visitante que implementa todas las especializaciones apropiadas de la función virtual. El visitante recibe como entrada la referencia de instancia e implementa el objetivo mediante despacho doble .

Los lenguajes de programación con tipos suma y coincidencia de patrones anulan muchas de las ventajas del patrón Visitor, ya que la clase Visitor puede bifurcarse fácilmente según el tipo de objeto y generar un error de compilación si se define un nuevo tipo de objeto que el Visitor 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 del Gang of Four .

Problemas que el patrón puede resolver

  • Debería ser posible definir una nueva operación para (algunas) clases de una estructura de objetos sin cambiar las clases.

Cuando se necesitan nuevas operaciones con frecuencia y la estructura del objeto consta de muchas clases no relacionadas, es inflexible agregar nuevas subclases cada vez que se requiere una nueva operación porque "distribuir todas estas operaciones entre las distintas clases de nodos conduce a un sistema difícil de entender, mantener y cambiar". [ 1 ]

Solución descrita por el patrón

  • Defina un objeto separado (visitante) que implemente una operación que se realizará sobre los elementos de una estructura de objeto.
  • Los clientes recorren la estructura del objeto y llaman a una operación de despacho llamada accept (visitor) sobre un elemento, que "despacha" (delega) la solicitud al "objeto visitante aceptado". El objeto visitante entonces realiza la operación sobre el elemento ("visita el elemento").

Esto permite crear nuevas operaciones independientemente de las clases de una estructura de objetos, añadiendo nuevos objetos visitantes.

Véase también el diagrama de clases y secuencia UML a continuación.

Definición

La Banda de los Cuatro define al Visitante como:

Representa una operación que se realizará sobre los elementos de una estructura de objeto. Visitor permite definir una nueva operación sin modificar las clases de los elementos sobre los que opera.

La naturaleza del patrón Visitor lo convierte en un patrón ideal para integrarse en API públicas, permitiendo así que sus clientes realicen operaciones en una clase utilizando una clase "visitante" sin tener que modificar el código fuente. [ 2 ]

Ventajas

Trasladar las operaciones a clases de visitantes es beneficioso cuando

  • Se requieren muchas operaciones no relacionadas sobre la estructura de un objeto.
  • Las clases que componen la estructura del objeto son conocidas y no se espera que cambien.
  • Es necesario añadir nuevas operaciones con frecuencia,
  • un algoritmo involucra varias clases de la estructura del objeto, pero se desea gestionarlo 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 dificulta las extensiones de la jerarquía de clases, ya que las nuevas clases normalmente 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) 2D. En su esencia, existen varios tipos para representar formas geométricas básicas como círculos, líneas y arcos. Las entidades se organizan en capas, y en la parte superior de la jerarquía de tipos se encuentra el dibujo, que es simplemente una lista de capas, junto con algunas propiedades adicionales.

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, podría parecer conveniente añadir métodos de guardado locales a todos los tipos de la jerarquía. Sin embargo, también puede ser útil guardar dibujos en otros formatos de archivo. Añadir cada vez más métodos para guardar en diferentes formatos de archivo puede complicar la estructura de datos geométrica original.

Una solución simplista sería mantener funciones separadas para cada formato de archivo. Dicha función de guardado tomaría un dibujo como entrada, lo procesaría y lo codificaría en ese formato específico. Al repetir este proceso para cada formato diferente, se acumula la duplicación entre las funciones. Por ejemplo, guardar un círculo en formato ráster requiere un código muy similar, independientemente del formato ráster utilizado, y es diferente al de otras formas primitivas. Lo mismo ocurre con otras formas primitivas como líneas y polígonos. De este modo, el código se convierte en un bucle externo extenso que recorre los objetos, con un gran árbol de decisiones dentro del bucle que consulta el tipo de objeto. Otro problema de este enfoque es que resulta muy fácil omitir una forma en uno o más guardadores, o bien, 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, su mantenimiento se vuelve más complejo.

En su lugar, se puede aplicar el patrón Visitor. Este patrón codifica la operación lógica (es decir, save(image_tree)) en toda la jerarquía en una sola clase (es decir, Saver) que implementa los métodos comunes para recorrer el árbol y describe 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 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 emite un error si se omite una forma, ya que ahora se espera en la función base común de recorrido/guardado.

Bucles de iteración

El patrón Visitor se puede usar para iterar sobre estructuras de datos tipo contenedor , al igual que el patrón Iterator , pero con funcionalidad limitada. [ 3 ] : 288 Por ejemplo, la iteración sobre una estructura de directorios 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 del contenido de los directorios implementando una funcionalidad Visitor para cada elemento, reutilizando el código de iteración. Se emplea ampliamente en sistemas Smalltalk y también se encuentra en C++. [ 3 ] : 289 Sin embargo, una desventaja de este enfoque es que no se puede salir del bucle fácilmente ni iterar concurrentemente (en paralelo, es decir, recorrer dos contenedores al mismo tiempo con una sola variable). [ 3 ] : 289 Esto último requeriría escribir funcionalidad adicional para que un Visitor admita estas características. [ 3 ] : 289i

Estructura

Diagrama de clases y secuencia 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 clases UML anterior, la clase no implementa una nueva operación directamente. En cambio, implementa una operación de despacho que "despacha" (delega) una solicitud al "objeto visitante aceptado" ( ). La clase implementa la operación ( ). luego implementa despachando a . La clase implementa la operación ( ).ElementAElementAaccept(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 objetos ( ) y llama a cada elemento. Primero, llama a , que llama al objeto aceptado. El elemento mismo ( ) se pasa a para que pueda "visitar" (llama a ). Posteriormente, llama a , que llama al que "visita" (llama a ).ClientElementA,ElementBaccept(visitor)Clientaccept(visitor)ElementAvisitElementA(this)visitorthisvisitorElementAoperationA()Clientaccept(visitor)ElementBvisitElementB(this)visitorElementBoperationB()

Diagrama de clases

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

Detalles

El patrón Visitor requiere un lenguaje de programación que admita despacho único , como lo hacen los lenguajes orientados a objetos comunes (como C++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python y C# ). Bajo esta condición, consideremos dos objetos, cada uno de algún tipo de clase; uno se denomina elemento y el otro visitante .

objetos

Visitante

El visitante declara un visitmétodo, que recibe el elemento como argumento, para cada clase de elemento. Los visitantes concretos derivan de la clase visitante e implementan estos visitmétodos, cada uno de los cuales implementa una parte del algoritmo que opera sobre la estructura del objeto. El estado del algoritmo se mantiene localmente mediante la clase visitante concreta.

Elemento

El elemento declara un acceptmétodo para aceptar un visitante, tomándolo como argumento. Los elementos concretos , derivados de la clase elemento, implementan este 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, suelen iterar sobre ellos, llamando al método de cada uno accept.

Cliente

El cliente crea la estructura del objeto, directa o indirectamente, e instancia los visitantes concretos. Cuando se va a realizar una operación implementada mediante el patrón Visitor, se llama al acceptmétodo del elemento o elementos de nivel superior.

Métodos

Aceptar

Cuando acceptse llama al método en el programa, su implementación se elige en función del tipo dinámico del elemento y del tipo estático del visitante. Cuando visitse llama al método asociado, su implementación se elige en función del tipo dinámico del visitante y del tipo estático del elemento, según se conoce dentro de la implementación del acceptmétodo, que es el mismo que el tipo dinámico del elemento. (Como ventaja adicional, si el visitante no puede manejar un argumento del tipo del elemento dado, el compilador detectará el error).

Visita

Por lo tanto, la implementación del visitmétodo se elige en función del tipo dinámico del elemento y del tipo dinámico del visitante. Esto implementa de manera efectiva el despacho doble . Para lenguajes cuyos sistemas de objetos admiten despacho múltiple, no solo despacho simple, como Common Lisp o C# a través del Dynamic Language Runtime (DLR), la implementación del patrón Visitor se simplifica enormemente (también conocido como Visitante Dinámico) al permitir el uso de sobrecarga de funciones simple para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que opere solo con datos públicos, cumple con el principio abierto/cerrado (ya que no modifica estructuras existentes) y con el principio de responsabilidad única (ya que implementa el patrón Visitor en un componente separado).

De esta forma, se puede escribir un algoritmo para recorrer un grafo 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.

Ejemplos

DO#

Este ejemplo declara una ExpressionPrintingVisitorclase independiente 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.

espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;interface IVisitor { void Visit ( Literal literal ); void Visit ( Addition addition ); }clase ExpressionPrintingVisitor : IVisitor { public void Visit ( Literal literal ) { Console . WriteLine ( literal . Value ); }public void Visit ( Addition addition ) { double leftValue = addition.Left.GetValue ( ); double rightValue = addition.Right.GetValue ( ) ; double sum = addition.GetValue ( ) ; Console.WriteLine ( $ "{ leftValue } + { rightValue} = { sum } " ) ; } }clase abstracta Expresión { público abstracto void Aceptar ( IVisitor visitante ); público abstracto doble ObtenerValor (); }clase Literal : Expresión { public Literal ( double valor ) { this . Valor = valor ; }public double Value { obtener ; establecer ; }public override void Accept ( IVisitor visitor ) { visitor.Visit ( this ) ; } public override double GetValue ( ) { return Value ; } }clase Adición : Expresión { public Adición ( Expresión izquierda , Expresión derecha ) { Izquierda = izquierda ; Derecha = derecha ; }public Expresión izquierda { obtener ; establecer ; } public Expresión derecha { obtener ; establecer ; }public override void Accept ( IVisitor visitor ) { Left.Accept ( visitor ) ; Right.Accept ( visitor ) ; visitor.Visit ( this ) ; } public override double GetValue ( ) { return Left.GetValue ( ) + Right.GetValue ( ) ; } }public static class Program { public static void Main ( string [] args ) { // Emular la suma 1 + 2 + 3 e = new ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) );ExpressionPrintingVisitor printingVisitor = new (); e . Accept ( printingVisitor ); Console . ReadKey (); } }

Charla informal

En este caso, es responsabilidad del objeto saber cómo imprimirse en un flujo de datos. El visitante es, por lo tanto, el objeto, no el flujo de datos.

"No existe una 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: '' package: 'Wikipedia' .Subclase de expresión : #Literal instanceVariableNames: 'value' classVariableNames: '' package: 'Wikipedia' .Clase Literal >>con: aValue "Método de clase para construir una instancia de la clase Literal" ^ self nuevo valor: aValue ; tú mismo .Literal >>valor: aValue "Establecedor para valor" valor := aValue .Literal >>putOn: aStream "Un objeto Literal sabe cómo imprimirse a sí mismo" aStream nextPutAll: valor comoString .Subclase de expresión : #Instancia de adición Nombres de variables: 'izquierda derecha' Nombres de variables de clase: '' paquete: 'Wikipedia' .Clase de suma >>izquierda: a derecha: b "Método de clase para construir una instancia de la clase de suma" ^ self nuevo izquierda: a ; derecha: b ; tú mismo .Adición >>izquierda: unaExpresión "Configurador para izquierda" izquierda := unaExpresión .Adición >>right: anExpression "Configurador para right" right := anExpression .Suma >>putOn: aStream "Un objeto de suma 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: '' package: 'Wikipedia' .Programa >> principal | flujo de expresiones | expresión := Suma izquierda: ( Suma izquierda: ( Literal con: 1 ) derecha: ( Literal con: 2 )) derecha: ( Literal con: 3 ) . flujo := ExpressionPrinter activado: ( Cadena nueva: 100 ) . flujo escribir: expresión . Transcripción mostrar: contenido del flujo . Transcripción vaciar .

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 Visitante interfaz { visitarRueda ( rueda Rueda ) cadena visitarMotor ( motor Motor ) cadena visitarCuerpo ( cuerpo Cuerpo ) cadena visitarCoche ( coche Coche ) cadena }

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, 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 nodos requieren acciones ligeramente diferentes para imprimir correctamente, CarElementPrintVisitordistribuye acciones en función de 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 Visitor con elementos de automóvil.

Fuentes

paquete org.wikipedia.examples ;import java.util.List ;interface CarElement { void accept ( CarElementVisitor visitor ); }interface CarElementVisitor { void visit ( Body body ); void visit ( Car car ); void visit ( Engine engine ); void visit ( Wheel wheel ); }clase Rueda implementa CarElement { privado final String nombre ;public Wheel ( final String name ) { this . name = name ; }public String getName () { return nombre ; }@Override public void accept ( CarElementVisitor visitor ) { /*  * accept(CarElementVisitor) en Wheel implementa  * accept(CarElementVisitor) en CarElement, por lo que la llamada  * a accept se vincula en tiempo de ejecución. Esto puede considerarse  * el *primer* despacho. Sin embargo, la decisión de llamar a  * visit(Wheel) (en lugar de visit(Engine), etc.) puede  * tomarse en tiempo de compilación, ya que se sabe en  tiempo de compilación * que 'this' es un Wheel. 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* despacho.  */ visitor . visit ( this ); } }class Body implements CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }class Engine implements CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }class Car implements CarElement { private final List < CarElement > elements ;public Car () { this . elements = List . of ( new Wheel ( "delantero izquierdo" ), new Wheel ( "delantero derecho" ), new Wheel ( "trasero izquierdo" ), new Wheel ( "trasero derecho" ), new Body (), new Engine () ); }@Override public void accept ( CarElementVisitor visitor ) { for ( CarElement element : elements ) { element . accept ( visitor ); } visitor . visit ( this ); } }class CarElementDoVisitor implements CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "Moviendo mi cuerpo" ); }@Override public void visit ( Car car ) { System . out . println ( "Arrancando mi coche" ); }@Override public void visit ( Wheel wheel ) { System . out . printf ( "Pateando mi %s rueda%n" , wheel . getName ()); }@Override public void visit ( Engine engine ) { System . out . println ( "Arrancando mi motor" ); } }class CarElementPrintVisitor implements CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "Visitando cuerpo" ); }@Override public void visit ( Car car ) { System . out . println ( "Visitando coche" ); }@Override public void visit ( Engine engine ) { System . out . println ( "Visitando el motor" ); }@Override public void visit ( Wheel wheel ) { System . out . printf ( "Visitando %s wheel%n" , wheel . getName ()); } }public class VisitorDemo { public static void main ( String [] args ) { Car car = new Car ();coche.aceptar ( nuevo CarElementPrintVisitor ( )); coche.aceptar ( nuevo CarElementDoVisitor ( ) ) ; } }

Producción

Visitando la rueda delantera izquierda Visitando la rueda delantera derecha Visitando la rueda trasera izquierda Visitando la rueda trasera derecha Órgano 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 Muevo mi cuerpo Arrancando el motor Arrancando mi coche 

Lisp común

Fuentes

( defclass auto () (( elementos :initarg :elements )))( defclass auto-part () (( name :initarg :name :initform "<unnamed-car-part>" )))( defmethod print-object (( p auto-part ) stream ) ( print-object ( slot-value p 'name ) stream ))( defclass rueda ( autoparte ) ())( defclass cuerpo ( autoparte ) ())( defclass motor ( autoparte ) ())( defgeneric traverse ( function object other-object ))( defmethod traverse ( function ( a auto ) other-object ) ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e other-object ))));; visitas para hacer algo;; captura todo ( defmethod hacer-algo ( objeto otro-objeto ) ( formato t "no sé cómo ~s y ~s deberían interactuar~%" objeto otro-objeto ));; visita que involucra rueda e entero ( defmethod hacer-algo (( objeto rueda ) ( otro-objeto entero )) ( formato t "pateando rueda ~s ~s veces~%" objeto otro-objeto ));; visita que involucra rueda y símbolo ( defmethod hacer-algo (( objeto rueda ) ( otro-objeto símbolo )) ( formato t "pateando rueda ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto ))( defmethod hacer-algo (( objeto motor ) ( otro-objeto entero )) ( formato t "iniciando motor ~s ~s veces~%" objeto otro-objeto ))( defmethod hacer-algo (( objeto motor ) ( otro-objeto símbolo )) ( formato t "iniciando el motor ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto ))( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'wheel :name "front-left-wheel" ) , ( make-instance 'wheel :name "front-right-wheel" ) , ( make-instance 'wheel :name "rear-left-wheel" ) , ( make-instance 'wheel :name "rear-right-wheel" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; recorrer para imprimir elementos ;; el flujo *standard-output* juega el papel de otro objeto aquí ( traverse #' imprimir una *standard-output* )( terpri ) ;; imprimir nueva línea;; recorrido con contexto arbitrario desde otro objeto ( recorrer #' hacer algo a 42 );; recorrido 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 "delantera izquierda" 42 veces pateando la rueda "delantera derecha" 42 veces pateando la rueda "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 usando el símbolo ABC

Notas

El other-objectparámetro es superfluo en traverse. La razón es que es posible usar una función anónima que llama al método de destino deseado con un objeto capturado léxicamente:

( defmethod traverse ( function ( a auto )) ;; otro objeto eliminado ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e )))) ;; desde aquí también;; ...;; forma alternativa de imprimir-recorrer ( recorrer ( lambda ( o ) ( imprimir o *salida estándar* )) a );; forma alternativa de hacer algo con ;; elementos de a y entero 42 ( recorrer ( lambda ( o ) ( hacer algo o 42 )) a )

Ahora bien, el despacho múltiple se produce en la llamada emitida desde el cuerpo de la función anónima, por lo que traversese trata simplemente de una función de mapeo que distribuye la aplicación de una función sobre los elementos de un objeto. De este modo, desaparecen todos los vestigios del patrón Visitor, salvo la función de mapeo, en la que no hay evidencia de que intervengan dos objetos. Todo el conocimiento sobre la existencia de dos objetos y el despacho sobre sus tipos reside en la función lambda.

Pitón

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 "visit" para los diferentes tipos de modelos deben tener nombres diferentes.

Fuentes

""" Ejemplo de patrón Visitor. """from abc import ABCMeta , abstractmethod from typing import NoReturnNO_IMPLEMENTADO : str = "Deberías implementar esto."clase CarElement ( metaclass = ABCMeta ): @abstractmethod def accept ( self , visitor : CarElementVisitor ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )clase Cuerpo ( CarElement ): def aceptar ( self , visitante : CarElementVisitor ) -> Ninguno : visitante . visit_body ( self )clase Engine ( CarElement ): def accept ( self , visitor : CarElementVisitor ) -> None : visitor . visit_engine ( self )clase Rueda ( CarElement ): def __init__ ( self , nombre : str ) -> Ninguno : self . nombre = nombredef accept ( self , visitor : CarElementVisitor ) - > None : visitor.visit_wheel ( self )clase Car ( CarElement ): def __init__ ( self ) -> None : self . elements : list [ CarElement ] = [ Wheel ( "delantero izquierdo" ), Wheel ( "delantero derecho" ), Wheel ( "trasero izquierdo" ), Wheel ( "trasero derecho" ), Body (), Engine () ]def accept ( self , visitor ) : for element in self.elements : element.accept ( visitor ) visitor.visit_car ( self )clase CarElementVisitor ( metaclass = ABCMeta ): @abstractmethod def visit_body ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_engine ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_wheel ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_car ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )clase CarElementDoVisitor ( CarElementVisitor ): def visit_body ( self , body : Body ) -> None : print ( "Moviendo mi cuerpo." )def visit_car ( self , car : Car ) -> None : print ( "Arrancando mi coche." )def visit_wheel ( self , wheel : Wheel ) -> None : print ( f "Pateando mi rueda { wheel . name } ." )def visit_engine ( self , engine : Engine ) -> None : print ( "Arrancando mi motor." )clase CarElementPrintVisitor ( CarElementVisitor ): def visit_body ( self , body : Body ) -> None : print ( "Visitando el cuerpo." )def visit_car ( self , car : Car ) -> None : print ( "Visitando el coche." )def visit_wheel ( self , wheel : Wheel ) -> None : print ( f "Visitando { wheel . name } wheel." )def visit_engine ( self , engine : Engine ) -> None : print ( "Visitando el motor." )if __name__ == "__main__" : car : Car = Car () car . accept ( CarElementPrintVisitor ()) car . accept ( CarElementDoVisitor ())

Producción

Visitando la rueda delantera izquierda. Visitando la rueda delantera derecha. Visitando la rueda trasera izquierda. Visitando la rueda trasera derecha. Visitando el cuerpo. 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. Arrancando mi motor. Arrancando 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 : Visitor ) -> Any : lookup : str = f "visit_ { self .__ qualname__ . replace ( "." , "_" ) } " return getattr ( visitor , lookup )( self )

Esto podría extenderse para iterar sobre el orden de resolución de métodos de la clase si se desea 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 similar al patrón visitante, sin diferenciar los tipos dentro de los objetos recorridos.
  • Codificación de Church : un concepto relacionado de 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 visitante emule variantes y patrones .

Véase también

Referencias

  1. 1 2 Gamma, Erich ; Helm, Richard ; Johnson, Ralph ; Vlissides, John (1994). Patrones de diseño: Elementos de software orientado a objetos reutilizable . Addison Wesley. págs. 331 y ss . ISBN  0-201-63361-2.
  2. Coogan, Corey (16 de junio de 2009). "Patrón de visitante: un ejemplo del mundo real" vía WordPress.com .
  3. 1 2 3 4 Budd, Timothy (1997). Introducción a la programación orientada a objetos (2.ª ed.). Reading, Mass.: Addison-Wesley. ISBN  0-201-82419-1OCLC 34788238 .​ 
  4. "El patrón de diseño Visitor: estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .{{cite web}}: CS1 mantenimiento: estado de la URL ( enlace )
  5. Reddy, Martin (2011). Diseño de API para C++ . Boston: Morgan Kaufmann. ISBN 978-0-12-385004-1OCLC 704559821 
  • La familia de patrones de diseño Visitor en Wayback Machine (archivado el 22 de octubre de 2015). Archivos adicionales: 12 de abril de 2004 , 5 de marzo de 2002. Un capítulo preliminar de * The Principles, Patterns, and Practices of Agile Software Development* , de Robert C. Martin , publicado por Prentice Hall.
  • Patrón Visitor 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.º 7, julio de 2006, páginas 23-30.
  • Artículo: Reconstrucción del patrón visitante desde una perspectiva de teoría de tipos.
  • Artículo " La esencia del patrón Visitor " de Jens Palsberg y C. Barry Jay . Artículo de 1997 de IEEE-CS COMPSAC que demuestra que los métodos accept() son innecesarios cuando se dispone de reflexión; 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() del patrón Visitor".
  • Patrón Visitor usando reflexión (Java).
  • El proyecto de código abierto PerfectJPattern proporciona una implementación del patrón Visitor en Java, libre de contexto y con tipado seguro, basada en delegados.
  • Patrón de diseño para visitantes