[sw design pattern] Visitor (Visitante)
Os padrões de design GoF são classificados em três categorias: Criacionais, Comportamentais e Estruturais. Os padrões criacionais tratam da criação de objetos, enquanto os padrões estruturais lidam com a estrutura das classes, como herança e composição. Por fim, os padrões comportamentais lidam com a comunicação entre objetos e suas interações. Estes padrões de projeto se encontram no livro “Padrões de Projetos: Soluções Reutilizáveis de Software Orientado a Objetos” escrito por 4 autores denominados GoF (Gang of Four).
O padrão de design Visitor é um padrão comportamental que permite separar um algoritmo de um objeto estrutura em que ele opera. Ele permite adicionar novos comportamentos a uma estrutura de objetos existente sem alterar essa estrutura. O padrão Visitor é composto por duas partes principais: o Visitor e o Element. O Visitor é uma classe abstrata que define os métodos de visita para cada tipo de Element. O Element é uma classe abstrata que define a interface para os objetos que o Visitor pode visitar. Quando um Visitor visita um Element, o Element passa uma referência de si mesmo para o Visitor. O Visitor, em seguida, chama o método de visita apropriado para o tipo de Element que está visitando. O Visitor pode então acessar os dados do Element e executar o algoritmo apropriado. O padrão Visitor é útil quando você tem uma estrutura de objetos complexa e deseja adicionar novos comportamentos a essa estrutura sem alterá-la. Ele também é útil quando você tem vários algoritmos que operam na mesma estrutura de objetos e deseja separar esses algoritmos da estrutura de objetos. Resumindo, o padrão Visitor é uma maneira flexível e poderosa de adicionar novos comportamentos a uma estrutura de objetos existente sem alterá-la. Ele permite que você separe o algoritmo da estrutura de objetos e adicione novos comportamentos de forma fácil e eficiente.
O padrão de design Visitor é frequentemente usado em conjunto com outros padrões de design, como o padrão Composite e o padrão Iterator. O padrão Composite é usado para criar estruturas de objetos hierárquicas, onde os objetos podem ser tratados como um único objeto ou como uma coleção de objetos. O padrão Visitor é útil em uma estrutura de objetos Composite porque permite que você adicione novos comportamentos a essa estrutura sem alterá-la. O Visitor pode visitar cada objeto na estrutura Composite e executar o comportamento apropriado.
O padrão Iterator é usado para percorrer uma coleção de objetos sem expor a estrutura interna da coleção. O padrão Visitor é útil em conjunto com o padrão Iterator porque permite que você adicione novos comportamentos a cada objeto na coleção sem expor a estrutura interna da coleção. O Visitor pode visitar cada objeto na coleção e executar o comportamento apropriado. O padrão Visitor também pode ser usado em conjunto com o padrão Strategy. O padrão Strategy é usado para definir uma família de algoritmos, encapsulá-los e torná-los intercambiáveis. O padrão Visitor pode ser usado para adicionar novos comportamentos a essa família de algoritmos sem alterar a estrutura existente. O Visitor pode visitar cada objeto na família de algoritmos e executar o comportamento apropriado.
Segue um exemplo de aplicação do padrão Visitor em Java:
// Define the Element interface
interface Element {
void accept(Visitor visitor);
}
// Define the ConcreteElement classes
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
// Perform operation A
}
}
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationB() {
// Perform operation B
}
}
// Define the Visitor interface
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
// Define the ConcreteVisitor classes
class ConcreteVisitor1 implements Visitor {
public void visit(ConcreteElementA element) {
element.operationA();
}
public void visit(ConcreteElementB element) {
element.operationB();
}
}
class ConcreteVisitor2 implements Visitor {
public void visit(ConcreteElementA element) {
// Perform a different operation on ConcreteElementA
}
public void visit(ConcreteElementB element) {
// Perform a different operation on ConcreteElementB
}
}
// Define the ObjectStructure class
class ObjectStructure {
private List<Element> elements = new ArrayList<Element>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
// Client code
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
objectStructure.accept(visitor1);
objectStructure.accept(visitor2);
Neste exemplo, temos a interface Element que define o método accept que recebe um Visitor. As classes ConcreteElementA e ConcreteElementB implementam a interface Element e implementam o método accept para chamar o método visit do Visitor apropriado.
A interface Visitor define os métodos visit para cada tipo de ConcreteElement. As classes ConcreteVisitor1 e ConcreteVisitor2 implementam a interface Visitor e definem o comportamento a ser executado para cada tipo de ConcreteElement.
A classe ObjectStructure mantém uma lista de Elements e define os métodos attach, detach e accept para adicionar, remover e aceitar visitas de Visitors.
No código do cliente, criamos uma instância de ObjectStructure e adicionamos ConcreteElementA e ConcreteElementB. Em seguida, criamos instâncias de ConcreteVisitor1 e ConcreteVisitor2 e chamamos o método accept de ObjectStructure para cada Visitor.
Algumas potenciais desvantagens da utilização do padrão Visitor:
- Pode ser difícil garantir que todos os objetos sejam visitados corretamente no padrão de design Visitor porque o visitante precisa ter conhecimento de toda a estrutura de objetos para visitar todos os objetos. Se a estrutura de objetos for complexa ou mudar com frequência, pode ser desafiador manter o visitante atualizado com todos os objetos que ele precisa visitar. Além disso, se novos tipos de objetos forem adicionados à estrutura, o visitante pode precisar ser atualizado para lidar com eles, o que pode ser demorado e propenso a erros. Finalmente, se o visitante depender da estrutura de objetos para fornecer informações sobre os objetos que ele precisa visitar, pode ser difícil garantir que a estrutura esteja sempre precisa e atualizada.
- Pode ser difícil garantir que o visitante e os objetos tenham a mesma interface no padrão de design Visitor porque o visitante precisa ter uma interface compatível com todos os tipos de objetos que ele precisa visitar. Se os objetos tiverem interfaces diferentes, pode ser difícil para o visitante visitá-los corretamente. Além disso, se novos tipos de objetos forem adicionados à estrutura, o visitante pode precisar ser atualizado para lidar com eles, o que pode exigir mudanças na interface do visitante. Isso pode ser demorado e propenso a erros, especialmente se a estrutura de objetos for grande e complexa.
- Pode ser complexo garantir que o padrão de design Visitor tenha acesso a informações suficientes sobre o objeto visitado porque o visitante precisa ter conhecimento suficiente sobre o objeto para realizar a visita corretamente. Se o objeto tiver muitos atributos ou métodos complexos, pode ser difícil para o visitante acessar todas as informações necessárias. Além disso, se o objeto tiver informações confidenciais ou protegidas, pode ser difícil para o visitante acessá-las sem violar o encapsulamento do objeto. Por fim, se o objeto tiver uma interface complexa ou mal documentada, pode ser difícil para o visitante entender como acessar as informações necessárias.
- No padrão de design Visitor, pode ser difícil adicionar novos tipos de objetos à estrutura existente sem modificar o visitante porque o visitante precisa ter conhecimento de todos os tipos de objetos que ele precisa visitar. Se um novo tipo de objeto for adicionado à estrutura, o visitante pode precisar ser atualizado para lidar com esse novo tipo de objeto. Isso pode exigir mudanças na interface do visitante e em sua implementação, o que pode ser demorado e propenso a erros. Além disso, se a estrutura de objetos for grande e complexa, pode ser difícil identificar todos os lugares onde o visitante precisa ser atualizado para lidar com o novo tipo de objeto.
- Pode ser complicado manter a coesão do visitante quando ele precisa lidar com muitos tipos diferentes de objetos no padrão de design Visitor. Isso ocorre porque o visitante precisa ter uma interface compatível com todos os tipos de objetos que ele precisa visitar, o que pode levar a uma interface grande e complexa. Além disso, se o visitante precisar lidar com muitos tipos diferentes de objetos, pode ser difícil manter a coesão do visitante, pois ele pode precisar ter muitas responsabilidades diferentes. Isso pode tornar o visitante difícil de entender e manter, especialmente se a estrutura de objetos for grande e complexa. Por fim, se o visitante precisar lidar com muitos tipos diferentes de objetos, pode ser difícil garantir que ele seja eficiente e escalável, pois pode precisar executar muitas operações diferentes para lidar com cada tipo de objeto.
- Pode trabalhoso garantir que o padrão de design Visitor não introduza dependências desnecessárias na estrutura existente porque o visitante precisa ter conhecimento de toda a estrutura de objetos para visitar todos os objetos. Se o visitante precisar acessar muitos objetos diferentes ou precisar executar muitas operações diferentes para visitar cada objeto, ele pode acabar introduzindo dependências desnecessárias na estrutura existente. Além disso, se o visitante precisar ser atualizado para lidar com novos tipos de objetos, ele pode acabar introduzindo dependências desnecessárias na estrutura existente. Isso pode tornar a estrutura mais difícil de entender e manter, especialmente se a estrutura de objetos for grande e complexa. Por fim, se o visitante introduzir dependências desnecessárias na estrutura existente, pode ser complexo garantir que a estrutura seja flexível e escalável, pois pode tornar difícil adicionar ou remover objetos sem afetar outras partes da estrutura.
links externos com mais exemplos do padrão Visitor:
—> O código do link abaixo apresenta uma implementação do padrão de design Visitor. Ele define uma classe abstrata Element e duas subclasses JsonElement e XmlElement. A classe Document contém uma lista de elementos e implementa o método accept que aceita um visitante e chama o método accept de cada elemento na lista. A classe ElementVisitor implementa a interface Visitor e define os métodos visit para cada tipo de elemento. A classe VisitorDemo cria um visitante e um documento com vários elementos e chama o método accept do documento com o visitante. Quando o método accept é chamado, cada elemento no documento chama o método visit correspondente no visitante. O resultado é a impressão de uma mensagem para cada elemento visitado, indicando o tipo de elemento e seu UUID.
https://www.baeldung.com/java-visitor-pattern
--> O código do site abaixo apresenta uma implementação em Java do padrão de design Visitor. Ele define uma interface VehicleInspector e três classes que implementam essa interface: VehicleInspection, Car, Van e Motorbike. A classe VehicleService contém um método calculateTotal que calcula o custo total do serviço para todos os veículos que passaram por um processo completo de serviço. O método calculateTotal usa um objeto VehicleInspector para visitar cada veículo e calcular o custo do serviço para cada um. A classe VehicleInspection implementa a interface VehicleInspector e define os métodos visit para cada tipo de veículo. Cada método visit calcula o custo do serviço para o veículo correspondente e retorna o valor. As classes Car, Van e Motorbike implementam a interface Vehicle e definem o método accept que aceita um VehicleInspector e chama o método visit correspondente no VehicleInspector. A classe VehicleService cria um array de veículos e chama o método calculateTotal para calcular o custo total do serviço para todos os veículos. O resultado é a impressão do custo total do serviço.
https://www.javatpoint.com/visitor-design-pattern-java
REFERÊNCIAS:
https://www.geeksforgeeks.org/visitor-design-pattern/
https://www.ionos.com/digitalguide/websites/web-development/visitor-pattern/
https://softwareengineering.stackexchange.com/questions/422386/visitor-design-pattern-usage