O padrão Visitor é um padrão de design comportamental que tem como objetivo principal separar um algoritmo das estruturas de objetos sobre as quais opera. Isso permite adicionar novas operações a essas estruturas sem modificar as estruturas em si. O padrão Visitor resolve vários problemas:
-
Adição de Operações sem Modificar Estruturas: Em sistemas com hierarquias de classes complexas, adicionar novas operações geralmente requer a modificação de cada classe na hierarquia. O Visitor permite adicionar operações sem mudar as classes dos elementos sobre os quais opera.
-
Agrupamento de Operações Relacionadas: Permite que operações relacionadas sejam agrupadas em um único visitante, mantendo a coesão e a separação de preocupações.
-
Suporte a Operações Complexas: Facilita a implementação de operações que dependem do tipo concreto dos elementos da hierarquia, permitindo que comportamentos específicos sejam aplicados com base no tipo real dos objetos visitados.
-
Acumulação de Estado: Os visitantes podem acumular estado durante a iteração pelos elementos, facilitando cálculos complexos entre múltiplos objetos.
Como Funciona
O padrão Visitor envolve os seguintes componentes:
- Visitor (Interface): Define uma interface para os visitantes concretos que implementam operações a serem executadas nos elementos da estrutura.
- Concrete Visitor: Classes que implementam a interface Visitor, cada uma executando uma operação específica.
- Element (Interface): Define uma interface para os elementos que podem ser visitados, incluindo um método para aceitar visitantes.
- Concrete Element: Classes concretas implementando a interface Element, cada uma definindo como aceitar um visitante.
- Object Structure: Uma estrutura (como uma coleção ou um composto) de elementos que podem ser visitados.
Exemplo
Um exemplo clássico do uso do padrão Visitor é em compiladores e interpretadores de linguagens de programação, onde uma estrutura de árvore sintática abstrata precisa ser percorrida para executar várias operações, como análise semântica, otimização ou geração de código. O Visitor permite adicionar essas operações sem modificar a estrutura da árvore.
Exemplo de Implementação
Vamos implementar um sistema de contabilidade para diferentes tipos de empregados de uma empresa, onde um visitante pode calcular bônus com base no tipo de empregado.
Interface Element (Empregado)
Primeiro, criamos a interface Empregado, que representa os elementos que podem ser visitados:
public interface Empregado {
void aceitar(Visitor visitor);
double getSalario();
}Classes Concrete Element (Desenvolvedor, Gerente)
Implementamos duas classes concretas para diferentes tipos de empregados:
public class Desenvolvedor implements Empregado {
private double salario;
public Desenvolvedor(double salario) {
this.salario = salario;
}
@Override
public void aceitar(Visitor visitor) {
visitor.visitar(this);
}
@Override
public double getSalario() {
return salario;
}
}
public class Gerente implements Empregado {
private double salario;
public Gerente(double salario) {
this.salario = salario;
}
@Override
public void aceitar(Visitor visitor) {
visitor.visitar(this);
}
@Override
public double getSalario() {
return salario;
}
}Interface Visitor
Definimos a interface Visitor:
public interface Visitor {
void visitar(Desenvolvedor desenvolvedor);
void visitar(Gerente gerente);
}Classe Concrete Visitor (BonusVisitor)
Implementamos um visitante concreto para calcular bônus:
public class BonusVisitor implements Visitor {
@Override
public void visitar(Desenvolvedor desenvolvedor) {
System.out.println("Bônus do Desenvolvedor: " + desenvolvedor.getSalario() * 0.10);
}
@Override
public void visitar(Gerente gerente) {
System.out.println("Bônus do Gerente: " + gerente.getSalario() * 0.20);
}
}Testando o Padrão Visitor
Usamos o visitante para calcular bônus para diferentes empregados:
public class Main {
public static void main(String[] args) {
Empregado[] empregados = new Empregado[]{
new Desenvolvedor(10000),
new Gerente(20000)
};
Visitor bonusVisitor = new BonusVisitor();
for (Empregado empregado : empregados) {
empregado.aceitar(bonusVisitor);
}
}
}Neste exemplo, Desenvolvedor e Gerente são empregados que implementam a interface Empregado. Cada um sabe como “aceitar” um Visitor. O BonusVisitor implementa a interface Visitor e calcula bônus para cada tipo de empregado. O método main demonstra como a operação de calcular bônus é aplicada aos empregados sem alterar suas classes. Isso permite adicionar facilmente novas operações de cálculo de bônus para diferentes tipos de empregados no futuro.