This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
wiki:pdd_10 [2024/09/02 00:15] – created admin | wiki:pdd_10 [2025/04/10 12:07] (current) – admin | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Visitor ====== | ====== Visitor ====== | ||
+ | |||
+ | **Contexto**: | ||
+ | |||
+ | **Problema**: | ||
+ | |||
+ | Sin embargo, el objetivo es implementar estas operaciones fuera de las clases de '' | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | interface Visitor { | ||
+ | void visit(Auto c); | ||
+ | void visit(Autobus o); | ||
+ | void visit(Motocicleta m); | ||
+ | } | ||
+ | |||
+ | class PrintVisitor implements Visitor { | ||
+ | public void visit(Auto c) { " | ||
+ | public void visit(Autobus o) { " | ||
+ | public void visit(Motocicleta m) {" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | PrintVisitor visitor = new PrintVisitor(); | ||
+ | foreach (Vehiculo vehiculo: listaDeVehiculosEstacionados) { | ||
+ | visitor.visit(vehiculo); | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
+ | 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**, | ||
+ | |||
+ | Para que quede más claro, el siguiente error ocurre al compilar el código anterior: | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | 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**, | ||
+ | |||
+ | <code 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. | ||
+ | |||
+ | <code java> | ||
+ | |||
+ | PrintVisitor visitor = new PrintVisitor(); | ||
+ | foreach (Vehiculo vehiculo: listaDeVehiculosEstacionados) { | ||
+ | vehiculo.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**, | ||
+ | |||
+ | 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. |