Acyclic Visitor
Propósito
Permitir añadir nuevas funciones a jerarquías de clases existentes sin que estas se vean afectadas, y sin crear los problemáticos círculos de dependencias que son inherentes al patrón GoF (Gang of Four) Visitor.
Explicación
Ejemplo del mundo real
Tenemos una jerarquía de clases módem. Los modems de esta jerarquía deben ser visitados por un algoritmo externo basándose en unos filtros (el módem es compatible con Unix o DOS).
En otras palabras
Acyclic Visitor permite añadir funciones a jerarquías de clases existentes sin modificarlas.
WikiWikiWeb dice
El patrón Acyclic Visitor permite que nuevas funciones sean añadidas a jerarquías de clases existentes sin afectar a las mismas, y sin crear los círculos de dependencias que son inherentes al patrón de visitante (Visitor Pattern) de GangOfFour.
Ejemplo Programático
Aquí tenemos la jerarquía Modem
.
public abstract class Modem {
public abstract void accept(ModemVisitor modemVisitor);
}
public class Zoom extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) {
((ZoomVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
}
}
}
public class Hayes extends Modem {
...
@Override
public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) {
((HayesVisitor) modemVisitor).visit(this);
} else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
}
}
}
Después tenemos la jerarquía ModemVisitor
.
public interface ModemVisitor {
}
public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes);
}
public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom);
}
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
}
public class ConfigureForDosVisitor implements AllModemVisitor {
...
@Override
public void visit(Hayes hayes) {
LOGGER.info(hayes + " used with Dos configurator.");
}
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Dos configurator.");
}
}
public class ConfigureForUnixVisitor implements ZoomVisitor {
...
@Override
public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator.");
}
}
Finalmente, aquí están los "visitors" en acción.
var conUnix = new ConfigureForUnixVisitor();
var conDos = new ConfigureForDosVisitor();
var zoom = new Zoom();
var hayes = new Hayes();
hayes.accept(conDos);
zoom.accept(conDos);
hayes.accept(conUnix);
zoom.accept(conUnix);
Output del programa:
// Hayes modem used with Dos configurator.
// Zoom modem used with Dos configurator.
// Only HayesVisitor is allowed to visit Hayes modem
// Zoom modem used with Unix configurator.
Diagrama de clases
Aplicación
Este patrón puede ser usado:
- Cuando necesitas añadir una nueva función a una jerarquía de clases sin que esta se vea afectada o alterada.
- Cuando hay funciones que operan sobre la jerarquía, pero no pertenecen a la jerarquía como tal. Las clases ConfigureForDOS / ConfigureForUnix / ConfigureForX por ejemplo.
- Cuando necesitas ejecutar operaciones muy diferentes en un objeto dependiendo de su tipo.
- Cuando la jerarquía visitada va a ser frecuentemente extendida con derivados de la clase elemento.
- Cuando el proceso de volver a compilar, enlazar, probar o distribuir los derivados de la clase elemento es muy pesado.
Tutoriales
Consecuencias
Buenas:
- No hay círculos de dependencias entre las jerarquías.
- No es necesario compilar todos los visitantes si se añade uno nuevo.
- No provoca errores de compilación en visitantes existentes si la jerarquía tiene un nuevo miembro.
Malas:
- Viola el Principio de sustitución de Liskov al mostrar que puede aceptar todos los visitantes solamente estando interesado en uno en particular.
- Hay que crear una jerarquía de visitantes paralela para todos los miembros de una jerarquía visitable.