En la programación orientada a objetos y la ingeniería de software , el patrón de diseño del visitante es una forma de separar un algoritmo de una estructura de objeto en la que opera. Un resultado práctico de esta separación es la capacidad de agregar nuevas operaciones a las estructuras de objetos existentes sin modificar las estructuras. Es una forma de seguir el principio abierto / cerrado .
En esencia, el visitante permite agregar nuevas funciones virtuales a una familia de clases , sin modificar las clases. En cambio, se crea una clase de visitante que implementa todas las especializaciones apropiadas de la función virtual. El visitante toma la referencia de la instancia como entrada e implementa el objetivo mediante un envío doble .
Descripción general
El patrón de diseño Visitor [1] es uno de los veintitrés patrones de diseño GoF bien conocidos 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 de visitante? [2]
- 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 en las diversas clases de nodos conduce a un sistema que es difícil para comprender, mantener y cambiar ". [1]
¿Qué solución describe el patrón de diseño de visitante?
- Defina un objeto separado (visitante) que implemente una operación que se realizará en elementos de una estructura de objeto.
- Los clientes atraviesan la estructura del objeto y llaman a una operación de envío aceptar (visitante) en un elemento - que "envía" (delega) la solicitud al "objeto visitante aceptado". A continuación, el objeto visitante realiza la operación sobre el elemento ("visita el elemento").
Esto hace posible crear nuevas operaciones independientemente de las clases de una estructura de objeto agregando nuevos objetos de visitante.
Consulte también el diagrama de secuencia y clase UML a continuación.
Definición
La Banda de los Cuatro define al Visitante como:
Representa [ing] una operación a realizar en elementos de una estructura de objeto. Visitor le permite definir una nueva operación sin cambiar las clases de los elementos sobre los que opera.
La naturaleza del visitante lo convierte en un patrón ideal para conectarse a las API públicas, lo que permite a sus clientes realizar operaciones en una clase utilizando una clase "visitante" sin tener que modificar la fuente. [3]
Usos
Mover 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, un inconveniente de este patrón es que dificulta las extensiones de la jerarquía de clases, ya que las nuevas clases normalmente requieren visit
que se agregue un nuevo método a cada visitante.
Ejemplo de caso de uso
Considere 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 está 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étrica original relativamente pura.
Una forma ingenua de resolver esto sería mantener funciones separadas para cada formato de archivo. Esta función de guardado tomaría un dibujo como entrada, lo atravesaría y codificaría en ese formato de archivo específico. Como esto se hace para cada formato diferente agregado, se acumula la duplicación entre las funciones. Por ejemplo, guardar una forma de círculo en un formato de ráster requiere un código muy similar sin importar qué forma de ráster 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 exterior que atraviesa los objetos, con un gran árbol de decisión dentro del bucle que consulta el tipo de objeto. Otro problema con este enfoque es que es muy fácil pasar por alto una forma en uno o más protectores, o se introduce una nueva forma primitiva, pero la rutina de guardado se implementa solo para un tipo de archivo y no para otros, lo que lleva a la extensión y el mantenimiento del código. problemas.
En su lugar, se puede aplicar el patrón de visitantes. Codifica una operación lógica en toda la jerarquía en una clase que contiene un método por tipo. En el ejemplo de CAD, cada función de guardado se implementaría como una subclase de visitante separada. Esto eliminaría toda duplicación de comprobaciones de tipo y pasos transversales. También haría que el compilador se quejara si se omite una forma.
Otro motivo es reutilizar el código de iteración. Por ejemplo, la iteración sobre una estructura de directorio podría implementarse con un patrón de visitante. Esto permitiría crear búsquedas de archivos, copias de seguridad de archivos, eliminación de directorios, etc., implementando un visitante para cada función mientras se reutiliza el código de iteración.
Estructura
Diagrama de secuencia y clase UML
![](http://wikiimg.tojsiabtv.com/wikipedia/commons/0/00/W3sDesign_Visitor_Design_Pattern_UML.jpg)
En el diagrama de clases de UML anterior , la ElementA
clase no implementa una nueva operación directamente. En su lugar, ElementA
implementa una operación de envío accept(visitor)
que "envía" (delega) una solicitud al "objeto visitante aceptado" ( visitor.visitElementA(this)
). La Visitor1
clase implementa la operación ( visitElementA(e:ElementA)
). ElementB
luego implementa accept(visitor)
enviando a visitor.visitElementB(this)
. La Visitor1
clase implementa la operación ( visitElementB(e:ElementB)
).
El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución: el Client
objeto atraviesa los elementos de una estructura de objeto ( ElementA,ElementB
) y llama accept(visitor)
a cada elemento.
Primero, las Client
llamadas accept(visitor)
on ElementA
, que invoca visitElementA(this)
el visitor
objeto aceptado . El elemento en sí ( this
) se pasa al visitor
para que pueda "visitar" ElementA
(llamar operationA()
).
A partir de entonces, las Client
llamadas accept(visitor)
sobre ElementB
, lo que exige visitElementB(this)
en los visitor
que las "visitas" ElementB
(llamadas operationB()
).
Diagrama de clase
![](http://wikiimg.tojsiabtv.com/wikipedia/en/thumb/e/eb/Visitor_design_pattern.svg/430px-Visitor_design_pattern.svg.png)
Detalles
El patrón de visitante requiere un lenguaje de programación que admita el envío ú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, considere dos objetos, cada uno de algún tipo de clase; uno se denomina elemento y el otro visitante .
El visitante declara un visit
método, que toma el elemento como argumento, para cada clase de elemento. Los visitantes concretos se derivan de la clase de visitante e implementan estos visit
métodos, cada uno de los cuales implementa parte del algoritmo que opera en la estructura del objeto. El estado del algoritmo es mantenido localmente por la clase de visitante concreta.
El elemento declara un accept
método para aceptar un visitante, tomando al visitante como argumento. Los elementos concretos , derivados de la clase de elemento, implementan el accept
método. En su forma más simple, esto no es más que una llamada al visit
método del visitante . Los elementos compuestos , que mantienen una lista de objetos secundarios, suelen iterar sobre ellos, llamando al accept
método de cada niño .
El cliente crea la estructura del objeto, directa o indirectamente, y ejemplifica a los visitantes concretos. Cuando se va a realizar una operación que se implementa utilizando el patrón Visitor, llama al accept
método de los elementos de nivel superior.
Cuando accept
se llama al método en el programa, su implementación se elige según el tipo dinámico del elemento y el tipo estático del visitante. Cuando visit
se llama al método asociado , su implementación se elige en función tanto del tipo dinámico del visitante como del tipo estático del elemento, como se conoce desde la implementación del accept
método, que es el mismo que el tipo dinámico del elemento. (Como beneficio adicional, si el visitante no puede manejar un argumento del tipo de elemento dado, el compilador detectará el error).
Por lo tanto, la implementación del visit
método se elige en función tanto del tipo dinámico del elemento como del tipo dinámico del visitante. Esto implementa efectivamente el doble despacho . Para los lenguajes cuyos sistemas de objetos admiten el envío múltiple, no solo el envío único, como Common Lisp o C # a través de Dynamic Language Runtime (DLR), la implementación del patrón de visitante se simplifica enormemente (también conocido como Dynamic Visitor) al permitir el uso de la sobrecarga de funciones simples para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que opere 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 de visitante en un componente separado).
De esta manera, se puede escribir un algoritmo para atravesar 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 elementos. visitantes.
Ejemplo de C #
Este ejemplo declara una ExpressionPrintingVisitor
clase separada que se encarga de la impresión.
espacio de nombres Wikipedia { public class ExpressionPrintingVisitor { public void PrintLiteral ( literal literal ) { Console . WriteLine ( literal . Valor ); }public void PrintAddition ( adición de suma ) { double leftValue = suma . Izquierda . GetValue (); double rightValue = suma . Correcto . GetValue (); var suma = suma . GetValue (); Consola . WriteLine ( "{0} + {1} = {2}" , leftValue , rightValue , suma ); } } Expresión de clase abstracta pública { aceptación vacía abstracta pública ( ExpressionPrintingVisitor v ); public abstract double GetValue (); }public class Literal : Expresión { public double Value { get ; establecer ; }public Literal ( valor doble ) { this . Valor = valor ; } public override void Accept ( ExpressionPrintingVisitor v ) { v . PrintLiteral ( esto ); }public override double GetValue () { Valor de retorno ; } } Suma de clase pública : Expresión { Expresión pública Izquierda { get ; establecer ; } derecho de expresión pública { get ; establecer ; } Suma pública ( Expresión a la izquierda , Expresión a la derecha ) { Izquierda = izquierda ; Derecha = derecha ; }public override void Accept ( ExpressionPrintingVisitor v ) { v . PrintAddition ( esto ); }public override double GetValue () { volver a la izquierda . GetValue () + Derecha . GetValue (); } }public static class Program { public static void Main ( string [] args ) { // Emulate 1 + 2 + 3 var e = new Addition ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) );var printingVisitor = new ExpressionPrintingVisitor (); e . Aceptar ( printingVisitor ); } } }
Ejemplo de Smalltalk
En este caso, es responsabilidad del objeto saber cómo imprimirse en una secuencia. El visitante aquí es entonces el objeto, no la corriente.
"No hay 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 : #Expression 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: unValor "Setter para el valor" Valor : = unValor .Literal >> putOn: aStream "Un objeto Literal sabe cómo imprimirse a sí mismo" aStream nextPutAll: valor asString . Subclase de expresión : #Addition 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 left: a ; derecha: b ; usted mismo .Suma >> left: anExpression "Setter for left" left : = anExpression .Adición >> derecha: anExpression "Setter para derecho" right : = anExpression .Además >> Puton: unFlujo "objeto una adición sabe cómo imprimir en sí" unFlujo nextPut: $ ( . Dejado Puton: unFlujo . UnFlujo nextPut: $ + . Derecho Puton: unFlujo . UnFlujo nextPut: $) . Subclase de objeto : #Program instanceVariableNames: '' classVariableNames: '' paquete: 'Wikipedia' .Programa >> principal | flujo de expresión | expresión : = Suma a la izquierda: ( Suma a la izquierda: ( Literal con: 1 ) derecha: ( Literal con: 2 )) derecha: ( Literal con: 3 ) . stream : = ExpressionPrinter en: ( Cadena nueva: 100 ) . secuencia de escritura: expresión . Transcripción del programa: contenido del flujo . Transcripción al ras .
Ejemplo de C ++
Fuentes
#include #include class AbstractDispatcher ; // Reenviar declarar AbstractDispatcherclass File { // Clase principal para los elementos (ArchivedFile, SplitFile y // ExtractedFile) public : // Esta función acepta un objeto de cualquier clase derivada de // AbstractDispatcher y debe implementarse en todas las clases derivadas virtual void Accept ( AbstractDispatcher & despachador ) = 0 ; };// Reenviar declarar elementos específicos (archivos) para ser enviados class ArchivedFile ; class SplitFile ; class ExtractedFile ;class AbstractDispatcher { // Declara la interfaz para el despachador public : // Declara sobrecargas para cada tipo de archivo para despachar virtual void Dispatch ( ArchivedFile & file ) = 0 ; Despacho vacío virtual ( SplitFile & file ) = 0 ; Despacho virtual vacío ( ExtractedFile & file ) = 0 ; }; class ArchivedFile : public File { // Elemento específico clase # 1 public : // Resuelto en tiempo de ejecución, llama a la función sobrecargada del despachador, // correspondiente a ArchivedFile. void Accept ( AbstractDispatcher y dispatcher ) override { dispatcher . Despacho ( * esto ); } };class SplitFile : public File { // Elemento específico clase # 2 public : // Resuelto en tiempo de ejecución, llama a la función sobrecargada del despachador, // correspondiente a SplitFile. void Accept ( AbstractDispatcher y dispatcher ) override { dispatcher . Despacho ( * esto ); } };class ExtractedFile : public File { // Elemento específico clase # 3 public : // Resuelto en tiempo de ejecución, llama a la función sobrecargada del despachador, // correspondiente a ExtractedFile. void Accept ( AbstractDispatcher y dispatcher ) override { dispatcher . Despacho ( * esto ); } };class Dispatcher : public AbstractDispatcher { // Implementa el envío de todo // tipo de elementos (archivos) public : void Dispatch ( ArchivedFile & ) override { std :: cout << "dispatching ArchivedFile" << std :: endl ; } void Dispatch ( SplitFile & ) override { std :: cout << "despachando SplitFile" << std :: endl ; } void Dispatch ( ExtractedFile & ) override { std :: cout << "despachando ExtractedFile" << std :: endl ; } };int main () { ArchivedFile archived_file ; SplitFile split_file ; ExtractedFile extract_file ; std :: vector < archivo *> archivos = { y archived_file , y split_file , y extracted_file , }; Despachador despachador ; para ( Archivo * archivo : archivos ) { archivo -> Aceptar ( despachador ); } }
Producción
despachando ArchivedFiledespachando SplitFiledespachando ExtractedFile
Ir ejemplo
Go no admite la sobrecarga, por lo que los métodos de visita necesitan nombres diferentes.
Fuentes
paquete principalimportar "fmt"type Visitor interface { visitWheel ( wheel Wheel ) string visitEngine ( engine Engine ) string visitBody ( body Body ) string visitCar ( car Car ) string }tipo de elemento interfaz { Aceptar ( visitante visitante ) cadena }escriba Wheel struct { name string }func ( w * Wheel ) Aceptar ( visitante Visitante ) cadena { visitante de retorno . visitWheel ( * w ) }func ( w * Rueda ) getName () string { return w . nombre }tipo estructura del motor {}func ( e * Engine ) Aceptar ( visitante Visitante ) cadena { visitante de retorno . visitEngine ( * e ) }tipo estructura del cuerpo {}func ( b * Cuerpo ) Aceptar ( visitante Visitante ) cadena { visitante de retorno . visitBody ( * b ) }tipo de coches struct { motor Motor cuerpo Cuerpo ruedas [ 4 ] ruedas }func ( c * Coche ) Aceptar ( visitante Visitante ) cadena { elementos : = [] elemento { & c . motor , y c . cuerpo , y c . ruedas [ 0 ], & c . ruedas [ 1 ], & c . ruedas [ 2 ], y c . ruedas [ 3 ], } res : = visitante . visitCar ( * c ) para _ , elem : = elementos de rango { res + = elem . Aceptar ( visitante ) } volver res } escriba PrintVisitor struct {}func ( pv * PrintVisitor ) visitWheel ( rueda de la rueda ) cadena { return fmt . Sprintln ( "visitando" , rueda . GetName (), "rueda" ) } func ( pv * PrintVisitor ) visitEngine ( motor Motor ) cadena { return fmt . Sprintln ( "motor de visitas" ) } func ( pv * PrintVisitor ) visitBody ( body Body ) cadena { return fmt . Sprintln ( "cuerpo visitante" ) } func ( pv * PrintVisitor ) visitCar ( car Car ) string { return fmt . Sprintln ( "coche de visita" ) }/ * salida: coche visitante visita motor visita cuerpo visita rueda delantera izquierda visita rueda delantera derecha visita rueda trasera izquierda visita rueda trasera derecha * / func main () { coche : = coche { motor : motor {}, carrocería : carrocería {}, ruedas : [ 4 ] Rueda { { "front left" }, { "front right" }, { "back left" }, { "back right" }, }, }visitante : = PrintVisitor {} res : = coche . Aceptar ( y visitante ) fmt . Println ( res ) }
Producción
coche de visitamotor visitantecuerpo visitantevisitando la rueda delantera izquierdavisitando la rueda delantera derechavisitando la rueda trasera izquierdavisitando la rueda trasera derecha
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 describiendo los componentes de un automóvil). En lugar de crear print
métodos para cada subclase nodo ( Wheel
, Engine
, Body
, y Car
), una clase visitante ( CarElementPrintVisitor
) realiza la acción de impresión deseado. Debido a que las diferentes subclases de nodos requieren acciones ligeramente diferentes para imprimirse correctamente, CarElementPrintVisitor
distribuye acciones según la clase del argumento pasado a su visit
método. CarElementDoVisitor
, que es análoga a una operación de guardado para un formato de archivo diferente, hace lo mismo.
Diagrama
Fuentes
import java.util.List ;interfaz CarElement { void accept ( visitante CarElementVisitor ); }interfaz CarElementVisitor { visita nula ( cuerpo del cuerpo ); vacío visita ( coche del coche ); visita nula ( motor motor ); visita nula ( rueda de la rueda ); } class Wheel implementa CarElement { nombre de cadena final privado ; Rueda pública ( nombre de la cadena final ) { this . nombre = nombre ; } public String getName () { nombre de retorno ; } @Override public void accept ( CarElementVisitor visitante ) { / * * accept (CarElementVisitor) en Wheel implements * accept (CarElementVisitor) en CarElement, por lo que la llamada * para aceptar está vinculada en tiempo de ejecución. Esto puede considerarse * el * primer * envío. Sin embargo, la decisión de llamar a * visit (Wheel) (a diferencia de visit (Engine), etc.) se puede * tomar durante el tiempo de compilación, ya que se sabe que 'this' en el momento de la compilación * es un Wheel. Además, cada implementación de * CarElementVisitor implementa la visita (Wheel), que es * otra decisión que se toma en tiempo de ejecución. Esto puede * considerarse el * segundo * envío. * / visitante . visitar ( esto ); } }class Body implementa CarElement { @Override public void accept ( visitante de CarElementVisitor ) { visitante . visitar ( esto ); } } class Engine implementa CarElement { @Override public void accept ( visitante de CarElementVisitor ) { visitante . visitar ( esto ); } } la clase de coches implementa CarElement { privado última Lista < CarElement > elementos ; public Car () { esto . elementos = Lista . de ( rueda nueva ( "delantera izquierda" ), rueda nueva ( "delantera derecha" ), rueda nueva ( "trasera izquierda" ), rueda nueva ( "trasera derecha" ), carrocería nueva (), motor nuevo () ); } @Override public void accept ( CarElementVisitor visitante ) { for ( CarElement element : elements ) { element . aceptar ( visitante ); } visitante . visitar ( esto ); } }class CarElementDoVisitor implementa CarElementVisitor { @Override public void visit ( Body body ) { System . fuera . println ( "Moviendo mi cuerpo" ); } @Anular visita pública nula ( automóvil automóvil ) { System . fuera . println ( "Arrancando mi coche" ); } @Anular visita de nulo público ( rueda de rueda ) { System . fuera . println ( "Pateando mi" + rueda . getName () + "rueda" ); } @Anular la visita nula pública ( motor motor ) { System . fuera . println ( "Arrancando mi motor" ); } } class CarElementPrintVisitor implementa CarElementVisitor { @Override public void visit ( Body body ) { System . fuera . println ( "Cuerpo visitante" ); } @Anular visita pública nula ( automóvil automóvil ) { System . fuera . println ( "Coche de visita" ); } @Anular la visita nula pública ( motor motor ) { System . fuera . println ( "Motor visitante" ); } @Anular visita de nulo público ( rueda de rueda ) { System . fuera . println ( "Visitando" + rueda . getName () + "rueda" ); } } public class VisitorDemo { public static void main ( final String [] args ) { Car car = new Car (); coche . aceptar ( nuevo CarElementPrintVisitor ()); coche . aceptar ( nuevo CarElementDoVisitor ()); } }
Producción
Visitando la rueda delantera izquierdaVisitando la rueda delantera derechaVisitando la rueda trasera izquierdaVisitando la rueda trasera derechaCuerpo visitanteMotor visitanteCoche de visitaPateando mi rueda delantera izquierdaPateando mi rueda delantera derechaPateando mi rueda trasera izquierdaPateando mi rueda trasera derechaMoviendo mi cuerpoArrancando mi motorArrancando mi auto
Ejemplo de Common Lisp
Fuentes
( defclass auto () (( elementos : initarg : elementos )))( defclass auto-part () (( nombre : initarg : nombre : initform " coche- sin nombre>" )))( Defmethod de impresión a objetos (( p auto-parte ) corriente ) ( impresión a objetos ( slot-valor p 'nombre ) corriente ))( rueda defclass ( autopartes ) ()) ( defclass body ( autopartes ) ())( motor defclass ( autopartes ) ()) ( defgeneric traverse ( función objeto otro-objeto ))( defmethod traverse ( función ( a auto ) otro-objeto ) ( con ranuras ( elementos ) a ( dolist ( e elementos ) ( función de llamada de función e otro-objeto ))));; visitas para hacer algo;; catch all ( defmethod do-something ( objeto otro-objeto ) ( formato t "no sé cómo ~ sy ~ s deben interactuar ~%" objeto otro-objeto ));; visita que involucra rueda y entero ( defmethod do-something (( rueda de objeto ) ( otro-objeto entero )) ( formato t "patear rueda ~ s ~ s veces ~%" objeto otro-objeto )) ;; visita que involucra rueda y símbolo ( defmethod do-something (( rueda de objeto ) ( símbolo de otro objeto )) ( formato t "patear la rueda ~ s simbólicamente usando el símbolo ~ s ~%" objeto otro-objeto )) ( defmethod do-something (( motor de objeto ) ( entero de otro objeto )) ( formato t "motor de arranque ~ s ~ s veces ~%" objeto otro-objeto )) ( defmethod do-something (( motor de objeto ) ( símbolo de otro objeto )) ( formato t "motor de arranque ~ 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- instancia 'motor : nombre "motor" ))))) ;; atravesar para imprimir elementos ;; flujo * salida-estándar * juega el papel de otro-objeto aquí ( atravesar #' imprimir una * salida-estándar * ) ( terpri ) ;; imprimir nueva línea ;; atravesar con contexto arbitrario de otro objeto ( atravesar # ' hacer algo a 42 ) ;; atravesar con contexto arbitrario de otro objeto ( atravesar # ' hacer algo a ' abc ))
Producción
"rueda delantera izquierda""rueda delantera derecha""rueda trasera izquierda""rueda trasera derecha""cuerpo""motor"patear la rueda "rueda delantera izquierda" 42 vecespatear la rueda "rueda delantera derecha" 42 vecespatear la rueda "rueda trasera izquierda" 42 vecespatear la rueda "rueda trasera derecha" 42 vecesno sé cómo deberían interactuar "cuerpo" y 42arrancar el motor "motor" 42 vecespatear la rueda "rueda delantera izquierda" simbólicamente usando el símbolo ABCpatear la rueda "rueda delantera derecha" simbólicamente usando el símbolo ABCpatear la rueda "rueda trasera izquierda" simbólicamente con el símbolo ABCpatear la rueda "rueda trasera derecha" simbólicamente con el símbolo ABCno sé cómo deberían interactuar "cuerpo" y ABCarrancar el motor "motor" mediante el símbolo ABC
Notas
El other-object
parámetro es superfluo en traverse
. La razón es que es posible utilizar una función anónima que llame al método de destino deseado con un objeto capturado léxicamente:
( defmethod traverse ( función ( a auto )) ;; otro-objeto eliminado ( con-ranuras ( elementos ) a ( dolist ( e elementos ) ( función funcall e )))) ;; de aqui tambien ;; ... ;; forma alternativa de imprimir-atravesar ( atravesar ( lambda ( o ) ( imprimir o * salida-estándar * )) a ) ;; forma alternativa de hacer algo con ;; elementos de ay entero 42 ( transversal ( lambda ( o ) ( hacer algo o 42 )) a )
Ahora, el envío múltiple ocurre en la llamada emitida desde el cuerpo de la función anónima, por lo que traverse
es solo una función de mapeo que distribuye una aplicación de función sobre los elementos de un objeto. Por lo tanto, desaparecen todos los rastros del patrón de visitante, excepto la función de mapeo, en la que no hay evidencia de que estén involucrados dos objetos. Todo el conocimiento de que hay dos objetos y un envío de sus tipos está 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. " ""de abc import ABCMeta , método abstractoNOT_IMPLEMENTED = "Debería implementar esto".clase CarElement : __metaclass__ = ABCMeta @abstractmethod def aceptar ( auto , visitante ): aumento NotImplementedError ( NOT_IMPLEMENTED )class Body ( CarElement ): def accept ( yo mismo , visitante ): visitante . visitBody ( yo )class Engine ( CarElement ): def accept ( yo mismo , visitante ): visitante . visitEngine ( auto )class Wheel ( CarElement ): def __init__ ( self , name ): self . nombre = nombre def aceptar ( yo mismo , visitante ): visitante . visitWheel ( yo )clase Coche ( CarElement ): def __init__ ( self ): self . elementos = [ Rueda ( "delantera izquierda" ), Rueda ( "delantera derecha" ), Rueda ( "trasera izquierda" ), Rueda ( "trasera derecha" ), Cuerpo (), Motor () ] def aceptar ( yo , visitante ): para elemento en sí mismo . elementos : elemento . aceptar ( visitante ) visitante . visitCar ( auto )clase CarElementVisitor : __metaclass__ = ABCMeta @abstractmethod def visitBody ( auto , elemento ): aumento NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitEngine ( auto , elemento ): aumento NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitWheel ( auto , elemento ): aumento NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self , element ): levanta NotImplementedError ( NOT_IMPLEMENTED )class 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." . Formato ( rueda . Nombre )) def visitEngine ( auto , motor ): print ( "Arrancando mi motor." )class CarElementPrintVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( "Visiting body." ) def visitCar ( self , car ): print ( "Visiting car." ) def visitWheel ( self , wheel ): print ( "Visiting {} rueda. " . formato ( rueda . nombre )) def visitEngine ( auto , motor ): print ( " Motor visitante. " )coche = Coche () coche . aceptar ( CarElementPrintVisitor ()) car . aceptar ( CarElementDoVisitor ())
Producción
Visitando la rueda delantera izquierda.Visitando la rueda delantera derecha.Visitando la rueda trasera izquierda.Visitando la rueda trasera derecha.Cuerpo visitante.Visitando el motor.Coche de visita.Pateando mi rueda delantera izquierda.Pateando mi rueda delantera derecha.Pateando mi rueda trasera izquierda.Pateando mi rueda trasera derecha.Moviendo mi cuerpo.Arrancando mi motor.Arrancando mi auto.
Abstracción
Si uno está usando Python 3 o superior, puede hacer una implementación general del método accept:
clase Visitable : def aceptar ( auto , visitante ): operaciones de búsqueda = "visit_" + Tipo de ( auto ) . __qualname__ . reemplazar ( "." , "_" ) return getattr ( visitante , búsqueda ) ( yo )
Se podría extender esto para iterar sobre el orden de resolución del método de la clase si quisieran recurrir a clases ya implementadas. También podrían usar la función de enlace de subclase para definir la búsqueda por adelantado.
Patrones de diseño relacionados
- Patrón de iterador : define un principio transversal como el patrón de visitante, sin hacer una diferenciación de tipo dentro de los objetos atravesados.
- Codificación de la iglesia : 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 de visitante emule variantes y patrones .
Ver también
- Tipo de datos algebraicos
- Despacho doble
- Envío múltiple
- Objeto de función
Referencias
- ↑ a b Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison Wesley. págs. 331ss . ISBN 0-201-63361-2.CS1 maint: varios nombres: lista de autores ( enlace )
- ^ "El patrón de diseño del visitante: problema, solución y aplicabilidad" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ Ejemplo del mundo real de patrón de visitante
- ^ "El patrón de diseño del visitante - Estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
enlaces externos
- The Visitor Family of Design Patterns 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 aproximado de Los principios, patrones y prácticas del desarrollo de software ágil , 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, no. 7, julio de 2006, páginas 23-30.
- Artículo A Reconstrucción teórica de tipos del patrón visitante
- Artículo " La esencia del patrón del visitante " por Jens Palsberg y C. Barry Jay . 1997 IEEE-CS COMPSAC paper que muestra que los métodos accept () son innecesarios cuando la reflexión está disponible; introduce el término 'Paseo' para la técnica.
- Artículo " Un tiempo para la reflexión " de Bruce Wallace - subtitulado "Las capacidades de reflexión de Java 1.2 eliminan los molestos métodos accept () de su patrón de visitante"
- Patrón de visitante usando reflexión (java).
- PerfectJPattern Open Source Project , proporciona una implementación libre de contexto y segura de tipos del patrón de visitantes en Java basada en delegados.
- Patrón de diseño de visitantes