====== Visitor ======
**Contexto**: Supongamos que existe un sistema de estacionamientos. Supongamos que en este sistema existe una clase ''Vehículo'', con subclases ''Auto'', ''Autobús'' y ''Motocicleta''. Estas clases se utilizan para almacenar información sobre los vehículos estacionados en el aparcamiento. Supongamos también que todos estos vehículos están almacenados en una lista. Decimos que esta lista es una estructura de datos polimórfica, ya que puede almacenar objetos de diferentes clases, siempre que sean subclases de ''Vehículo''.
**Problema**: Con frecuencia, en el sistema de estacionamientos, necesitamos realizar una operación en todos los vehículos estacionados. Como ejemplo, podemos mencionar: imprimir información sobre los vehículos estacionados, persistir los datos de los vehículos o enviar un mensaje a los dueños de los vehículos.
Sin embargo, el objetivo es implementar estas operaciones fuera de las clases de ''Vehículo'' a través de un código como el siguiente:
interface Visitor {
void visit(Auto c);
void visit(Autobus o);
void visit(Motocicleta m);
}
class PrintVisitor implements Visitor {
public void visit(Auto c) { "imprime datos de auto" }
public void visit(Autobus o) { "imprime datos de autobus" }
public void visit(Motocicleta m) {"imprime datos de moto"}
}
En este código, la clase **PrintVisitor** incluye métodos que imprimen los datos de un Auto, Autobús y Motocicleta. Una vez implementada esta clase, nos gustaría usar el siguiente código para visitar todos los vehículos del aparcamiento:
PrintVisitor visitor = new PrintVisitor();
foreach (Vehiculo vehiculo: listaDeVehiculosEstacionados) {
visitor.visit(vehiculo); // error de compilación
}
Sin embargo, en el código mostrado, el método **visit** que se debe llamar depende del tipo dinámico del objeto destino de la llamada (**visitor**) y del tipo dinámico de un parámetro (**vehículo**). No obstante, en lenguajes como Java, C++ o C#, solo se considera el tipo del objeto destino de la llamada para elegir qué método invocar. En otras palabras, en Java y lenguajes similares, el compilador solo conoce el tipo estático de **vehículo**, que es **Vehículo**. Por eso, no puede inferir qué implementación de **visit** debe llamarse.
Para que quede más claro, el siguiente error ocurre al compilar el código anterior:
visitor.visit(vehiculo);
^
method PrintVisitor.visit(Auto) is not applicable
(argument mismatch; Vehiculo cannot be converted to Auto)
method PrintVisitor.visit(Autobus) is not applicable
(argument mismatch; Vehiculo cannot be converted to Autobus)
De hecho, este código solo compila en lenguajes que ofrecen **despacho doble** de llamadas a métodos (**double dispatch**). En estos lenguajes, se utilizan los tipos del objeto destino y de uno de los parámetros de la llamada para elegir el método que se invocará. Sin embargo, el **despacho doble** solo está disponible en lenguajes más antiguos y menos conocidos hoy en día, como Common Lisp.
Por lo tanto, nuestro problema es el siguiente: ¿cómo simular **double dispatch** en un lenguaje como Java? Si logramos hacerlo, podremos evitar el error de compilación que ocurre en el código que mostramos.
Solución: La solución a nuestro problema consiste en utilizar el patrón de diseño **Visitor**. Este patrón define cómo añadir una operación a una familia de objetos, sin necesidad de modificar las clases de los mismos. Además, el patrón **Visitor** debe funcionar incluso en lenguajes con **single dispatching** de métodos, como Java.
Como primer paso, debemos implementar un método **accept** en cada clase de la jerarquía. En la clase raíz, este método es abstracto. En las subclases, recibe como parámetro un objeto del tipo **Visitor**. Y su implementación simplemente llama al método **visit** de ese **Visitor**, pasando **this** como parámetro. Sin embargo, como la llamada ocurre en el cuerpo de una clase, el compilador conoce el tipo de **this**. Por ejemplo, en la clase **Auto**, el compilador sabe que el tipo de **this** es **Auto**. Así que sabe que debe llamar a la implementación de **visit** que tiene **Auto** como parámetro. Para ser precisos, el método exacto que se llamará depende del tipo dinámico del objeto destino de la llamada (**v**). Sin embargo, esto no es un problema, ya que significa que tenemos un caso de **single dispatch**, que está permitido en lenguajes como Java.
abstract class Vehiculo {
abstract public void accept(Visitor v);
}
class Auto extends Vehiculo {
...
public void accept(Visitor v) {
v.visit(this);
}
...
}
class Autobus extends Vehiculo {
...
public void accept(Visitor v) {
v.visit(this);
}
...
}
// Idem para Motocicleta
Por último, debemos modificar el bucle que recorre la lista de vehículos estacionados. Ahora, llamaremos a los métodos **accept** de cada vehículo, pasando el **visitor** como parámetro.
PrintVisitor visitor = new PrintVisitor();
foreach (Vehiculo vehiculo: listaDeVehiculosEstacionados) {
veiculo.accept(visitor);
}
En resumen, los **visitors** facilitan la adición de un método en una jerarquía de clases. Un **visitor** agrupa operaciones relacionadas —en el ejemplo, la impresión de datos de **Vehículo** y de sus subclases—. Pero también podría existir un segundo **visitor**, con otras operaciones —por ejemplo, persistir los objetos en disco—. Por otro lado, la adición de una nueva clase en la jerarquía, por ejemplo, **Camión**, requerirá la actualización de todos los **visitors** con un nuevo método: **visit(Camión)**.
Antes de concluir, es importante mencionar que los **visitors** tienen una desventaja importante: pueden forzar una ruptura en el encapsulamiento de las clases que serán visitadas. Por ejemplo, **Vehículo** podría tener que implementar métodos públicos que expongan su estado interno para que los **visitors** puedan acceder a él.