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:

Representa [ing] 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:  anExpression  "Establecedor de izquierda"  left  :=  anExpression .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 visitaEngine ( 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 implements CarElement { private final String name; public Wheel(final String name) { this.name = name; } public String getName() { return name; } @Override public void accept(CarElementVisitor visitor) { /* * accept(CarElementVisitor) in Wheel implements * accept(CarElementVisitor) in CarElement, so the call * to accept is bound at run time. This can be considered * the *first* dispatch. However, the decision to call * visit(Wheel) (as opposed to visit(Engine) etc.) can be * made during compile time since 'this' is known at compile * time to be a Wheel. Moreover, each implementation of * CarElementVisitor implements the visit(Wheel), which is * another decision that is made at run time. This can be * considered the *second* dispatch. */ 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("front left"), new Wheel("front right"), new Wheel("back left"), new Wheel("back right"), 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("Moving my body"); } @Override public void visit(Car car) { System.out.println("Starting my car"); } @Override public void visit(Wheel wheel) { System.out.println("Kicking my " + wheel.getName() + " wheel"); } @Override public void visit(Engine engine) { System.out.println("Starting my engine"); }}class CarElementPrintVisitor implements CarElementVisitor { @Override public void visit(Body body) { System.out.println("Visiting body"); } @Override public void visit(Car car) { System.out.println("Visiting car"); } @Override public void visit(Engine engine) { System.out.println("Visiting engine"); } @Override public void visit(Wheel wheel) { System.out.println("Visiting " + wheel.getName() + " wheel"); }}public class VisitorDemo { public static void main(final String[] args) { Car car = new Car(); car.accept(new CarElementPrintVisitor()); car.accept(new CarElementDoVisitor()); }}


Output

Visiting front left wheelVisiting front right wheelVisiting back left wheelVisiting back right wheelVisiting bodyVisiting engineVisiting carKicking my front left wheelKicking my front right wheelKicking my back left wheelKicking my back right wheelMoving my bodyStarting my engineStarting my car

Common Lisp example

Sources

(defclass auto () ((elements :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 wheel (auto-part) ())(defclass body (auto-part) ())(defclass engine (auto-part) ())(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))));; do-something visitations;; catch all(defmethod do-something (object other-object) (format t "don't know how ~s and ~s should interact~%" object other-object));; visitation involving wheel and integer(defmethod do-something ((object wheel) (other-object integer)) (format t "kicking wheel ~s ~s times~%" object other-object));; visitation involving wheel and symbol(defmethod do-something ((object wheel) (other-object symbol)) (format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object))(defmethod do-something ((object engine) (other-object integer)) (format t "starting engine ~s ~s times~%" object other-object))(defmethod do-something ((object engine) (other-object symbol)) (format t "starting engine ~s symbolically using symbol ~s~%" object other-object))(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"))))) ;; traverse to print elements ;; stream *standard-output* plays the role of other-object here (traverse #'print a *standard-output*) (terpri) ;; print newline ;; traverse with arbitrary context from other object (traverse #'do-something a 42) ;; traverse with arbitrary context from other object (traverse #'do-something a 'abc))

Output

"front-left-wheel""front-right-wheel""rear-left-wheel""rear-right-wheel""body""engine"kicking wheel "front-left-wheel" 42 timeskicking wheel "front-right-wheel" 42 timeskicking wheel "rear-left-wheel" 42 timeskicking wheel "rear-right-wheel" 42 timesdon't know how "body" and 42 should interactstarting engine "engine" 42 timeskicking wheel "front-left-wheel" symbolically using symbol ABCkicking wheel "front-right-wheel" symbolically using symbol ABCkicking wheel "rear-left-wheel" symbolically using symbol ABCkicking wheel "rear-right-wheel" symbolically using symbol ABCdon't know how "body" and ABC should interactstarting engine "engine" symbolically using symbol ABC

Notes

The other-object parameter is superfluous in traverse. The reason is that it is possible to use an anonymous function that calls the desired target method with a lexically captured object:

(defmethod traverse (function (a auto)) ;; other-object removed (with-slots (elements) a (dolist (e elements) (funcall function e)))) ;; from here too ;; ... ;; alternative way to print-traverse (traverse (lambda (o) (print o *standard-output*)) a) ;; alternative way to do-something with ;; elements of a and integer 42 (traverse (lambda (o) (do-something o 42)) a)

Now, the multiple dispatch occurs in the call issued from the body of the anonymous function, and so traverse is just a mapping function that distributes a function application over the elements of an object. Thus all traces of the Visitor Pattern disappear, except for the mapping function, in which there is no evidence of two objects being involved. All knowledge of there being two objects and a dispatch on their types is in the lambda function.

Python example

Python does not support method overloading in the classical sense (polymorphic behavior according to type of passed parameters), so the "visit" methods for the different model types need to have different names.

Sources

"""Visitor pattern example."""from abc import ABCMeta, abstractmethodNOT_IMPLEMENTED = "You should implement this."class CarElement(metaclass=ABCMeta): @abstractmethod def accept(self, visitor): raise NotImplementedError(NOT_IMPLEMENTED)class Body(CarElement): def accept(self, visitor): visitor.visitBody(self)class Engine(CarElement): def accept(self, visitor): visitor.visitEngine(self)class Wheel(CarElement): def __init__(self, name): self.name = name def accept(self, visitor): visitor.visitWheel(self)class Car(CarElement): def __init__(self): self.elements = [ Wheel("front left"), Wheel("front right"), Wheel("back left"), Wheel("back right"), Body(), Engine() ] def accept(self, visitor): for element in self.elements: element.accept(visitor) visitor.visitCar(self)class CarElementVisitor(metaclass=ABCMeta): @abstractmethod def visitBody(self, element): raise NotImplementedError(NOT_IMPLEMENTED) @abstractmethod def visitEngine(self, element): raise NotImplementedError(NOT_IMPLEMENTED) @abstractmethod def visitWheel(self, element): raise NotImplementedError(NOT_IMPLEMENTED) @abstractmethod def visitCar(self, element): raise NotImplementedError(NOT_IMPLEMENTED)class CarElementDoVisitor(CarElementVisitor): def visitBody(self, body): print("Moving my body.") def visitCar(self, car): print("Starting my car.") def visitWheel(self, wheel): print("Kicking my {} wheel.".format(wheel.name)) def visitEngine(self, engine): print("Starting my engine.")class CarElementPrintVisitor(CarElementVisitor): def visitBody(self, body): print("Visiting body.") def visitCar(self, car): print("Visiting car.") def visitWheel(self, wheel): print("Visiting {} wheel.".format(wheel.name)) def visitEngine(self, engine): print("Visiting engine.")car = Car()car.accept(CarElementPrintVisitor())car.accept(CarElementDoVisitor())

Output

Visiting front left wheel.Visiting front right wheel.Visiting back left wheel.Visiting back right wheel.Visiting body.Visiting engine.Visiting car.Kicking my front left wheel.Kicking my front right wheel.Kicking my back left wheel.Kicking my back right wheel.Moving my body.Starting my engine.Starting my car.

Abstraction

Using Python 3 or above allows to make a general implementation of the accept method:

class Visitable: def accept(self, visitor): lookup = "visit_" + type(self).__qualname__.replace(".", "_") return getattr(visitor, lookup)(self)

One could extend this to iterate over the class's method resolution order if they would like to fall back on already-implemented classes. They could also use the subclass hook feature to define the lookup in advance.

Related design patterns

See also

References

  1. ^ a b Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 331ff. ISBN 0-201-63361-2.{{cite book}}: CS1 maint: multiple names: authors list (link)
  2. ^ Visitor pattern real-world example
  3. ^ a b c d Budd, Timothy (1997). An introduction to object-oriented programming (2nd ed.). Reading, Mass.: Addison-Wesley. ISBN 0-201-82419-1. OCLC 34788238.
  4. ^ "The Visitor design pattern - Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
  5. ^ Reddy, Martin (2011). API design for C++. Boston: Morgan Kaufmann. ISBN 978-0-12-385004-1. OCLC 704559821.

External links