El patrón Visitor (Visitante) permite agregar nuevas operaciones sobre una estructura de objetos, sin cambiar las clases de los elementos sobre los que opera.
El patrón Visitor resuelve la dificultad de añadir nuevas operaciones o comportamientos a estructuras de objetos complejas, sin modificarlas.
La solución viene mediante un nuevo objeto, el “Visitor“, que contiene las operaciones a realizar.
Las clases de objetos a visitar aceptan al Visitor y le delegan la ejecución de la operación.
Esto permite añadir funcionalidades específicas sin modificar las clases de los objetos.
Este patrón es recomendable cuando:
Separación de Responsabilidades: Separa las operaciones, de los objetos sobre los que operan.
Extensibilidad: Facilita la adición de nuevas operaciones sin cambiar las clases de los elementos.
Agrupación de Operaciones: Permite agrupar operaciones relacionadas.
Rompe el Encapsulamiento: Los Visitors necesitan acceso a los elementos internos de las clases que visitan, lo que puede romper el encapsulamiento.
Complejidad: Añade una capa de complejidad al diseño, especialmente en la interacción entre los Visitors y los elementos.
Dificultades con Estructuras Cambiantes: Si la estructura de los elementos cambia seguido, es costoso mantener los Visitors actualizados.
Debemos desarrollar un sistema de contabilidad para una firma que maneja una gran variedad de cuentas y transacciones financieras.
Este sistema necesita ser capaz de generar distintos tipos de reportes financieros, como balances generales, estados de flujo de efectivo, y análisis de costos para diferentes tipos de cuentas y productos financieros.
El principal desafío es estructurar el sistema para que pueda interactuar con una variedad de cuentas y productos financieros para generar diferentes tipos de reportes, sin que el código se vuelva monolítico y difícil de mantener.
A medida que la firma crece y las regulaciones cambian, también deberemos agregar rápidamente nuevos tipos de reportes y manejar nuevos tipos de productos financieros, sin tener que alterar la base de código existente.
Elegimos el patrón Visitor porque nos permite separar las operaciones (los reportes) de las estructuras de objetos (las cuentas y productos financieros) sobre las que operan.
Con Visitor, podemos añadir nuevas operaciones sin cambiar las clases de los elementos que visitamos. Esto hace que nuestro sistema sea más fácil de mantener y expandir, permitiendo a los desarrolladores agregar nuevos tipos de reportes y manejar nuevos productos financieros, sin modificar las clases existentes.
Definimos la interfaz Element, Account, y el Visitor, AccountVisitor. Los elementos concretos, los tipos de cuenta. El visitor concreto, FinancialReportVisitor, para generar reportes. Y finalmente el Cliente, que instancia los objetos para generar el reporte.
Codificamos en Java lo que preparamos en el diagrama.
Definimos la Interfaz Element:
interface Account {
void accept(AccountVisitor visitor);
}
Definimos la Interfaz Visitor:
interface AccountVisitor {
void visit(SavingsAccount account);
void visit(LoanAccount account);
}
Creamos las clases ConcreteElement:
class SavingsAccount implements Account {
double balance;
SavingsAccount(double balance) {
this.balance = balance;
}
public void accept(AccountVisitor visitor) {
visitor.visit(this);
}
}
class LoanAccount implements Account {
double owedAmount;
LoanAccount(double owedAmount) {
this.owedAmount = owedAmount;
}
public void accept(AccountVisitor visitor) {
visitor.visit(this);
}
}
Creamos las clases ConcreteVisitor:
class FinancialReportVisitor implements AccountVisitor {
// Acumuladores para datos del reporte
double totalSavings;
double totalLoans;
public void visit(SavingsAccount account) {
totalSavings += account.balance;
}
public void visit(LoanAccount account) {
totalLoans += account.owedAmount;
}
void displayReport() {
System.out.println("Total Savings: " + totalSavings);
System.out.println("Total Loans: " + totalLoans);
}
}
El código cliente:
public class AccountingSystem {
public static void main(String[] args) {
Account[] accounts = new Account[]{
new SavingsAccount(1000),
new LoanAccount(500)
// Más cuentas
};
FinancialReportVisitor reportVisitor = new FinancialReportVisitor();
for(Account account : accounts) {
account.accept(reportVisitor);
}
reportVisitor.displayReport();
}
}
Los participantes que vimos antes son: Visitor, ConcreteVisitor, Element, ConcreteElement, Client:
La solución que conseguimos con el patrón Visitor fue eficaz y flexible. Al separar los reportes de las estructuras de las cuentas, hemos logrado que el sistema pueda adaptarse y expandirse fácilmente para incluir nuevos tipos de reportes y productos financieros.
Introduce cierta complejidad al requerir una serie de clases e interfaces adicionales, pero los beneficios al lograr que el sistema sea más fácil de mantener y expandir, lo hacen ideal para sistemas de este estilo, que requieren una gran variedad de operaciones sobre estructuras de datos complejas y en constante evolución.
El patrón Visitor puede ser usado para aplicar una operación sobre un objeto Composite.
Visitor puede ser usado para interpretar una estructura de objetos, como un árbol de sintaxis.