stringtranslate.com

Patrón de visitante

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 estructuras de objetos existentes sin modificar las estructuras. Es una forma de seguir el principio abierto/cerrado en programación orientada a objetos e 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 la instancia como entrada e implementa el objetivo mediante doble envío .

Los lenguajes de programación con tipos de suma y coincidencia de patrones obvian muchos de los beneficios del patrón de visitante, ya que la clase de visitante puede bifurcarse fácilmente en el tipo de objeto y generar un error de compilación si se define un nuevo tipo de objeto, lo cual hace el visitante. Aún no lo manejo.

Descripción general

El patrón de diseño Visitante [1] es uno de los veintitrés patrones de diseño conocidos de la Banda de los Cuatro 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 Visitante?

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 comprender, mantener y cambiar". [1]

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

Esto hace posible crear nuevas operaciones independientemente de las clases de una estructura de objetos agregando nuevos objetos visitantes.

Consulte también el diagrama de secuencia y clase UML a continuación.

Definición

La Banda de los Cuatro define al Visitante como:

Representar una operación que se 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 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

Mover las operaciones a clases de visitantes es beneficioso cuando

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

Considere el diseño de un sistema de diseño asistido por computadora (CAD) 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, además de 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 resulta útil poder guardar dibujos en otros formatos de archivo. Agregar cada vez más métodos para guardar en muchos formatos de archivos 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 guardar 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 de círculo en formato ráster requiere un código muy similar sin importar qué forma ráster específica se utilice, 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 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 mantenimiento del código. problemas. A medida que crecen las versiones de un 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, guardar (image_tree)) en toda la jerarquía en una clase (es decir, Saver) que implementa los métodos comunes para atravesar el árbol y describe los métodos auxiliares virtuales (es decir, save_circle, save_square, etc.) que se implementarán para formatear comportamientos específicos. En el caso del ejemplo de CAD, dichos comportamientos específicos del formato serían implementados por una subclase de Visitante (es decir, SaverPNG). Como tal, se elimina toda duplicación de comprobaciones de tipo y pasos transversales. Además, el compilador ahora se queja si se omite una forma, ya que ahora la función común de recorrido/guardado de base la espera.

Bucles de iteración

El patrón de visitante se puede utilizar para la iteración sobre estructuras de datos tipo contenedor , 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 de un patrón de bucle más convencional . Esto permitiría obtener información útil del contenido de los directorios mediante la implementación de una funcionalidad de visitante para cada elemento y al mismo tiempo reutilizar 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, atravesando dos contenedores al mismo tiempo mediante una sola ivariable). [3] : 289  Esto último requeriría escribir funciones adicionales para que un visitante admita estas características. [3] : 289 

Estructura

Diagrama de secuencia y clase UML

Un diagrama de clases UML de muestra y un 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 su lugar, implementa una operación de envío que "envía" (delega) una solicitud al "objeto visitante aceptado" ( ). La clase implementa la operación ( ). luego se 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 atraviesa los elementos de una estructura de objeto ( ) y llama a cada elemento. Primero, las llamadas a , que llama al objeto aceptado. El elemento en sí ( ) se pasa a para que pueda "visitar" (llamar ). A partir de entonces, el llama a , que llama a quien "visita" (llama ).ClientElementA,ElementBaccept(visitor)
Clientaccept(visitor)ElementAvisitElementA(this)visitorthisvisitorElementAoperationA()
Clientaccept(visitor)ElementBvisitElementB(this)visitorElementBoperationB()

Diagrama de clase

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

Detalles

El patrón de visitante requiere un lenguaje de programación que admita el envío único , como lo hacen los lenguajes comunes orientados a objetos (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 visitmé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 visitmétodos, cada uno de los cuales implementa parte del algoritmo que opera en la estructura del objeto. El estado del algoritmo lo mantiene localmente la clase de visitante concreta.

El elemento declara un acceptmétodo para aceptar un visitante, tomándolo como argumento. Los elementos concretos , derivados de la clase de 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 los iteran y llaman al acceptmétodo de cada niño.

El cliente crea la estructura del objeto, directa o indirectamente, y crea instancias de los visitantes concretos. Cuando se va a realizar una operación que se implementa utilizando el patrón Visitante, llama al acceptmétodo de los elementos de nivel superior.

Cuando acceptse llama al método en el programa, su implementación se elige en función tanto del tipo dinámico del elemento como del tipo estático del visitante. Cuando se llama al método asociado visit, 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 acceptmétodo, que es lo mismo que el tipo dinámico del elemento. (Como beneficio adicional, si el visitante no puede manejar un argumento del tipo de elemento dado, entonces el compilador detectará el error).

Por 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 efectivamente el doble despacho . Para los lenguajes cuyos sistemas de objetos admiten envío múltiple, no solo 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 una sobrecarga de funciones simple para cubrir todos los casos que se visitan. Un visitante dinámico, siempre que opere únicamente 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 según los tipos dinámicos tanto de los elementos como de los elementos. visitantes.

Ejemplo de C#

Este ejemplo declara una ExpressionPrintingVisitorclase separada que se encarga de la impresión. Si se desea la introducción de 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 Suma) permanecerán sin cambios.

usando Sistema ; espacio de nombres Wikipedia ; visitante de interfaz pública { visita vacía ( literal literal ); visita nula ( adición adicional ); }         clase pública ExpressionPrintingVisitor : Visitante { visita pública vacía ( literal literal ) { Consola . WriteLine ( literal . Valor ); }            visita pública vacía ( adición adicional ) { doble valor izquierdo = adición . Izquierda . ObtenerValor (); doble rightValue = suma . Bien . ObtenerValor (); var suma = suma . ObtenerValor (); Consola . WriteLine ( "{0} + {1} = {2}" , valorizquierdo , valorderecho , suma ); } }                      Expresión de clase abstracta pública { vacío abstracto público Aceptar ( Visitante v ); resumen público doble GetValue (); }             Literal de clase pública : Expresión { Literal público ( valor doble ) { this . Valor = valor ; }             Valor doble público { obtener ; colocar ; }       anulación pública nula Aceptar ( Visitante v ) { v . Visita ( este ); } anulación pública doble GetValue () { Valor de retorno ; } }                Adición de clase pública : Expresión { Adición pública ( Expresión izquierda , Expresión derecha ) { Izquierda = izquierda ; Derecha = derecha ; }                  Expresión pública izquierda { get ; colocar ; } Expresión pública correcta { get ; colocar ; }              anulación pública nula Aceptar ( Visitante v ) { Izquierda . Aceptar ( v ); Bien . Aceptar ( v ); v . Visita ( este ); } anulación pública doble GetValue () { retorno Izquierda . ObtenerValor () + Derecha . ObtenerValor (); } }                    programa de clase estática pública { public static void Main ( string [] args ) { // Emular 1 + 2 + 3 var e = nueva adición ( nueva adición ( nuevo literal ( 1 ), nuevo literal ( 2 ) ), nuevo literal ( 3) ) );                          var printVisitor = new ExpressionPrintingVisitor (); mi . Aceptar ( impresiónVisitante ); Consola . Leer la clave (); } }       

Ejemplo de pequeña charla

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 existe una sintaxis para crear una clase. Las clases se crean enviando mensajes a otras clases". Subclase WriteStream  :  #ExpressionPrinter  instanciaVariableNames:  ''  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 .  ^  un objeto . Subclase  de objeto : #Expresión  instanciaVariableNames:  ''  classVariableNames:  ''  paquete:  'Wikipedia' . Subclase  de expresión : #Literal  instanciaVariableNames:  'valor'  claseVariableNames:  ''  paquete:  'Wikipedia' .Clase literal  >>con:  aValue  "Método de clase para construir una instancia de la clase Literal"  ^ valor  nuevo propio  : aValue ; tú mismo .   Literal >>valor:  aValue  "Establecedor de valor"  valor  : =  aValue .Literal >>putOn:  aStream  "Un objeto Literal sabe cómo imprimirse"  aStream  nextPutAll:  valor  asString . Subclase  de expresión : #Addition  instanciaVariableNames:  'izquierda derecha'  claseVariableNames:  ''  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 ;  tú mismo .Adición >>izquierda:  unaExpresión  "Establecedor de izquierda"  izquierda  :=  unaExpresión .Adición >>derecha:  unaExpresión  "Establecedor de la derecha"  derecha  : =  unaExpresión .Suma >>putOn:  aStream  "Un objeto Suma sabe cómo imprimirse"  aStream  nextPut:  $( .  left  putOn:  aStream .  aStream  nextPut:  $+ .  right  putOn:  aStream .  aStream  nextPut:  $) . Subclase  de objeto : #Programa  instanciaVariableNames:  ''  classVariableNames:  ''  paquete:  'Wikipedia' .Programa >> principal  |  flujo de expresión  | expresión := Suma izquierda: ( Suma izquierda: ( Literal con: 1 ) derecha: ( Literal con: 2 )) derecha: ( Literal con: 3 ) . flujo : = ExpressionPrinter en: ( Cadena nueva: 100 ) . escritura de flujo : expresión . "Programa de transcripción : contenido de la transmisión ". Transcripción al ras .                           


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 { visitaRueda ( rueda Rueda ) cadena visitaMotor ( motor Motor ) cadena visitaCuerpo ( cuerpo Cuerpo ) cadena visitaCoche ( 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, Bodyy Car), una clase de visitante ( CarElementPrintVisitor) realiza la acción de impresión requerida. Debido a que diferentes subclases de nodos requieren acciones ligeramente diferentes para imprimirse correctamente, CarElementPrintVisitordistribuye acciones según la clase del argumento pasado a su visitmétodo. CarElementDoVisitor, que es análoga a una operación de guardar para un formato de archivo diferente, hace lo mismo.

Diagrama

Fuentes

importar java.util.List ; interfaz  CarElement { vacío aceptar ( visitante CarElementVisitor ); }    interfaz  CarElementVisitor { visita nula ( Cuerpo cuerpo ) ; visita nula ( Car car ); visita nula ( Motor motor ); visita nula ( Rueda rueda ); }             class  Wheel implementa CarElement { nombre de cadena final privada ;        Rueda pública ( nombre de cadena final ) { this . nombre = nombre ; }         cadena pública getName () { nombre de retorno ; }       @Override public void aceptar ( CarElementVisitor visitante ) { /*  * aceptar(CarElementVisitor) en Wheel implementa  * aceptar(CarElementVisitor) en CarElement, por lo que la llamada  * a aceptar está vinculada en tiempo de ejecución. Esto puede considerarse  * el *primer* envío. Sin embargo, la decisión de llamar  * visit(Wheel) (a diferencia de visit(Engine), etc.) se puede  * tomar durante el tiempo de compilación ya que se sabe que 'esto' en el  momento de la * compilación es una Rueda. Además, cada implementación de  * CarElementVisitor implementa la visita (Rueda), que es  * otra decisión que se toma en tiempo de ejecución. Este puede  * considerarse el *segundo* envío.  */ visitante . visitar ( esto ); } }        class  Body implementa CarElement { @Override public void aceptar ( visitante CarElementVisitor ) { visitante . visitar ( esto ); } }           class  Engine implementa CarElement { @Override public void aceptar ( visitante CarElementVisitor ) { visitante . visitar ( esto ); } }           class  Car implementa CarElement { elementos privados finales List <CarElement> ;        Coche público () { este . elementos = Lista . de ( nueva rueda ( "delantera izquierda" ), nueva rueda ( "delantera derecha" ), nueva rueda ( "detrás izquierda" ), nueva rueda ( "detrás derecha" ), nueva carrocería (), nuevo motor () ); }                    @Override public void aceptar ( visitante CarElementVisitor ) { para ( elemento CarElement : elementos ) { elemento . aceptar ( visitante ); } visitante . visitar ( esto ); } }               class  CarElementDoVisitor implements CarElementVisitor { @Override visita nula pública ( Cuerpo cuerpo ) { System . afuera . println ( "Mover mi cuerpo" ); }            @Override visita nula pública ( Coche coche ) { System . afuera . println ( "Arrancar mi auto" ); }        @Override visita nula pública ( Rueda rueda ) { System . afuera . println ( "Pateando mi " + rueda . getName () + "rueda" ); }            @Override visita nula pública ( Motor motor ) { System . afuera . println ( "Arrancando mi motor" ); } }       class  CarElementPrintVisitor implementa CarElementVisitor { @Override visita nula pública ( Cuerpo cuerpo ) { System . afuera . println ( "Organismo visitante" ); }            @Override visita nula pública ( Coche coche ) { System . afuera . println ( "Coche de visita" ); }        @Override visita nula pública ( Motor motor ) { System . afuera . println ( "Motor de visitas" ); }        @Override visita nula pública ( Rueda rueda ) { System . afuera . println ( "Visitando" + rueda . getName () + "rueda" ); } }           clase pública VisitorDemo { public static void main ( final String [] args ) { Coche coche = nuevo Coche ();                auto . aceptar ( nuevo CarElementPrintVisitor ()); auto . aceptar ( nuevo CarElementDoVisitor ()); } }    


Producción

Visita a la rueda delantera izquierdaVisitando la rueda delantera derechaVisitando la rueda trasera izquierdaVisitando la rueda trasera derechaOrganismo 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 Lisp común

Fuentes

( defclass auto () (( elementos :initarg :elementos )))     ( defclass auto-part () (( nombre :initarg :nombre :initform "<pieza-de-coche-sin nombre>" )))       ( defmethod objeto de impresión (( p auto-part ) flujo ) ( objeto de impresión ( valor de ranura p ' nombre ) flujo ))         ( rueda defclass ( autoparte ) ())   ( cuerpo defclass ( autoparte ) ())   ( motor defclass ( autoparte ) ())   ( recorrido defgenérico ( función objeto otro-objeto ))    ( recorrido defmethod ( función ( a auto ) otro objeto ) ( con ranuras ( elementos ) a ( dolist ( e elementos ) ( funcall función e otro objeto ))))               ;; visitas para hacer algo;; capturar todo ( defmethod hacer algo ( objeto otro-objeto ) ( formato t "no sé cómo deben interactuar ~s y ~s~%" objeto otro-objeto ))        ;; visita que involucra rueda y número entero ( defmethod hacer algo (( objeto rueda ) ( otro objeto entero )) ( formato t "rueda que patea ~s ~s veces~%" objeto otro-objeto ))          ;; visita que involucra rueda y símbolo ( defmethod hacer algo (( objeto rueda ) ( símbolo de otro objeto )) ( formato t "rueda que patea ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto ))          ( defmethod hacer algo (( objeto motor ) ( otro objeto entero )) ( formato t "arrancar motor ~s ~s veces~%" objeto otro-objeto ))          ( defmethod hacer algo (( objeto motor ) ( símbolo de otro objeto )) ( formato t "arrancar el motor ~s simbólicamente usando el símbolo ~s~%" objeto otro-objeto ))          ( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'rueda :nombre "rueda delantera izquierda" ) , ( make-instance 'rueda :nombre "rueda delantera derecha" ) , ( make-instance 'rueda :nombre "rueda-trasera-izquierda" ) , ( make-instance 'rueda :nombre "rueda-trasera-derecha" ) , ( make-instance 'cuerpo :nombre "cuerpo" ) , ( make- instancia 'motor :nombre "motor" ))))) ;; atravesar para imprimir elementos ;; la secuencia *salida-estándar* desempeña el papel de otro objeto aquí ( traverse #' imprime una *salida-estándar* )                                   ( terpri ) ;; imprimir nueva línea  ;; atravesar con contexto arbitrario desde otro objeto ( traverse #' hacer algo a 42 )     ;; atravesar con contexto arbitrario desde otro objeto ( traverse #' 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 veces.Patear la rueda "rueda trasera derecha" 42 veces.No sé cómo deberían interactuar "cuerpo" y 42.arrancar el motor "motor" 42 vecesPatear la rueda "rueda delantera izquierda" simbólicamente usando el símbolo ABCPateando la rueda "rueda delantera derecha" simbólicamente usando el símbolo ABCrueda de patada "rueda trasera izquierda" simbólicamente usando el símbolo ABCrueda de patada "rueda trasera derecha" simbólicamente usando el símbolo ABCNo 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 utilizar una función anónima que llame al método de destino deseado con un objeto capturado léxicamente:

( defmethod transversal ( función ( a auto )) ;; otro objeto eliminado ( con ranuras ( elementos ) a ( dolist ( e elementos ) ( funcall función 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 ( atravesar ( 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 traversees solo una función de mapeo que distribuye una aplicación de función sobre los elementos de un objeto. Así, todos los rastros del Patrón de Visitante desaparecen, excepto la función de mapeo, en la que no hay evidencia de que dos objetos estén involucrados. Todo el conocimiento de que hay dos objetos y un envío sobre 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ías implementar esto."clase  CarElement ( metaclase = ABCMeta ):  @abstractmethod  def  aceptar ( self ,  visitante ):  generar  NotImplementedError ( NOT_IMPLEMENTED ) cuerpo de clase ( CarElement ):  def  aceptar ( self ,  visitante ):  visitante . visitaCuerpo ( yo )clase  Motor ( CarElement ):  def  aceptar ( self ,  visitante ):  visitante . visitarEngine ( yo )clase  Rueda ( CarElement ):  def  __init__ ( self ,  nombre ):  self . nombre  =  nombre  def  aceptar ( auto ,  visitante ):  visitante . visitWheel ( yo )clase  Coche ( CarElement ):  def  __init__ ( self ):  self . elementos  =  [  Rueda ( "delantera izquierda" ),  Rueda ( "delantera derecha" ),  Rueda ( "detrás izquierda" ),  Rueda ( "detrás derecha" ),  Carrocería (),  Motor ()  ] def  aceptar ( self ,  visitante ):  para  elemento  en  self . elementos :  elemento . aceptar ( visitante )  visitante . visitarCoche ( yo )clase  CarElementVisitor ( metaclass = ABCMeta ):  @abstractmethod  def  visitBody ( self ,  elemento ):  levanta  NotImplementedError ( NOT_IMPLEMENTED )  @abstractmethod  def  visitEngine ( self ,  elemento ):  levanta  NotImplementedError ( NOT_IMPLEMENTED )  @abstractmethod  def  visitWheel ( self ,  elemento ):  levanta  NotImplementedError ( NOT_IMPLEMENTED )  @abstractmethod  def  visitCar ( self ,  elemento ):  generar  NotImplementedError ( NOT_IMPLEMENTED )class  CarElementDoVisitor ( CarElementVisitor ):  def  visitBody ( self ,  body ):  print ( "Mover mi cuerpo." )  def  visitCar ( self ,  car ):  print ( "Arrancar mi auto." )  def  visitWheel ( self ,  rueda ):  print ( "Pateando mi {} rueda." formato ( rueda . nombre ) )  def  visitEngine ( self ,  motor ):  print ( "Arrancando mi motor" ).class  CarElementPrintVisitor ( CarElementVisitor ):  def  visitBody ( self ,  cuerpo ):  print ( "Cuerpo visitante." )  def  visitCar ( self ,  auto ):  print ( "Coche visitante." )  def  visitWheel ( self ,  rueda ):  print ( "Visitante {} rueda." . formato ( rueda . nombre ))  def  visitEngine ( self ,  motor ):  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. Cuerpo 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. Moviendo mi cuerpo. Arrancando mi motor. Arrancando mi auto.

Abstracción

Usar Python 3 o superior permite realizar una implementación general del método de aceptación:

clase  Visitable :  def  aceptar ( self ,  visitante ):  búsqueda  =  "visit_"  +  tipo ( self ) . __cualnombre__ . reemplazar ( "." ,  "_" )  devolver  getattr ( visitante ,  búsqueda ) ( auto )

Se podría extender esto para iterar sobre el orden de resolución del método de la clase si quisiera recurrir a clases ya implementadas. También podrían utilizar la función de enlace de subclase para definir la búsqueda por adelantado.

Patrones de diseño relacionados

Ver también

Referencias

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

enlaces externos