C# - O padrão Comportamental Gof - Visitor
Neste artigo vou apresentar o padrão comportamental Gof Visitor. |
Segundo a
definição da Gang Of Four(GoF), o padrão
Visitor representa uma operação a ser realizada
sobre os elementos da estrutura de um objeto.
Este padrão
permite que criar uma nova operação sem precisar mudar a classe dos elementos
sobre as quais ela opera, e, isto é uma forma de separar um algoritmo da
estrutura de um objeto. O padrão permite também adicionar novos comportamentos à
hierarquia de classes existente sem alterar nenhum código existente.
Desta forma o padrão Visitor separa os
comportamentos não relacionados do objeto e os coloca em um objeto separado
criando um objeto separado para cada nova funcionalidade.
O uso deste padrão
é recomendado quando você tem operações distintas e não relacionadas para
executar em uma estrutura de objetos. A sua utilização evita adicionar código em
toda a estrutura do objeto.
O padrão Visitor não é um padrão muito comum devido à sua complexidade e
aplicabilidade limitada.
Este padrão é aderente aos seguintes princípios SOLID
:
1- Princípio da Responsabilidade Única (SRP)
2- Princípio Aberto/Fechado (OCP)
Na sua implementação o seu objeto pode delegar responsabilidades a diferentes
objetos. Por Exemplo:
- Você tem um objeto Funcionario responsável pelas
operações CRUD;
- Mas precisa adicionar outra responsabilidade ao objeto
Funcionario como gerar relatórios;
Usando o padrão Visitor você pode adicionar
comportamentos para gerar relatórios sem modificar o objeto
Funcionario.
Basta criar uma interface Visitor e definir a nova
funcionalidade que deseja implementar na classe
Funcionario. Assim o seu objeto Funcionario
segue o princípio Aberto/Fechado, pois esta incluindo um novo
comportamento sem precisar alterar o objeto.
Visitor : Exemplo
Vamos entender a atuação deste padrão com um exemplo.
- Você possui um tipo de estrutura de dados que consiste de um grande número de nós:
Na figura temos a representação simplificada de 4 nós de elementos do tipo X.
O despacho duplo é
um recurso que você pode usar na linguagem C# para controlar como a comunicação
flui entre dois objetos. Um uso frequente do recurso é passar o "this"
para uma função em outra classe, permitindo que essa classe se comunique de
volta ou manipule a instância do objeto de chamada.
Então como contornar esse problema ?
O padrão Visitor fornece uma solução para este cenário e implementa o despacho
duplo. A solução proposta é aplicar o padrão Visitor onde a usamos uma classe
especial chamada Visitor que pode realizar uma operação em um único nó.
- A classe tem um método Visit que recebe um nó
como um argumento e realizar a operação requerida no nó;
- Existe um subclasse Visitor para cada operação a
ser realizada;
- Existe também uma estrutura de dados complexa consistindo de um ou mais nós;
- Cada nó é chamado de um Element e possui um único
método chamado Accept que é fornecido para o
visitor;
- O método Accept recebe o visitor como um
argumento e chama o seu método Visit com este
ponteiro como um argumento;
Temos aqui representados:
1º Despacho – Client
chama o método virtual Element.Accept
2º Despacho – Element
chama o método polimórfico Visitor.Visit
O objeto Visitor irá visitar cada elemento da
estrutura do objeto e realizar a operação comum. (É possível variar a
operação conforme a variação do Visitor)
Diagrama UML
O diagrama UML do padrão Visitor segundo o Gof apresenta os seguintes participantes
1- Visitor - Declara uma operação Visit para
cada classe de ConcreteElement na estrutura do
objeto. O nome e a assinatura da operação identificam a classe que envia a
solicitação Visit ao Visitor.
Isso permite ao visitante determinar a classe concreta do elemento que está
sendo visitado. Em seguida, o visitante pode acessar os elementos diretamente
por meio de sua interface específica
2- ConcreteVisitor - Implementa cada operação
declarada pelo Visitor. Cada operação implementa um fragmento do algoritmo
definido para a classe ou objeto correspondente na estrutura.
3- Element - Define uma operação
Accept que toma um Visitor como argumento.
4- ConcreteElement - Implementa uma operação
Accept que leva um Visitor como argumento.
5- ObjectStructure - - Pode enumerar seus
elementos e fornece uma interface permitindo que Visitor visite seus elementos.
Quando podemos usar o padrão
Podemos usar o padrão Visitor nos seguintes cenários :
- Quando uma
estrutura de objeto tem muitas operações não relacionadas para ser executar.
- Quando a estrutura de um objeto não pode ser alterada, mas você precisa
realizar novas operações nela.
- As operações precisam ser executadas nas classes concretas de uma estrutura de
objeto.
-Quando as operações devem ser capazes de operar em múltiplas estruturas de
objetos que implementam a mesma interface.
Vantagens do padrão
Como vantagens deste padrão temos que :
- Adicionar uma
ação a todos os objetos Element de um estrutura é fácil, pois você só
precisa implementar a interface Visitor. Não há
necessidade de modificar todos os objetos Element para adicionar uma ação.
- Permite reagrupar ações comuns a muitos objetos Element em uma única
classe de Visitor. Apenas o código para essa ação está nessa classe Visitor.
Desvantagem
E como desvantagem
podemos citar que :
O código dos objetos Element estão espalhados em todos os objetos Visitor.
Portanto, a lógica do objeto Element vive em muitas classes. Isso torna o código
mais difícil de ler se você quiser examinar o código de um objeto Element;
- É necessário uma nova classe Visitor para cada ação;
Aplicação prática do padrão
Vamos supor que temos uma concessionária que possui uma coleção de veículos onde temos definido, o nome, o modelo e o preço. Assim podemos obter o preço normal do veiculo usando a classe Carro.
Agora precisamos aplicar um desconto promocional ao preço dos carros, mas queremos fazer isso sem ter que alterar a classe Carro.
Para isso vamos
implementar o padrão Visitor implementando um
PrecoVisitor que vai visitar o preço do carro
e vai calcular e retornar o preço do veiculo com desconto. Assim vamos
incluir uma nova operação na classe sem alterá-la
segundo o principio aberto fechado.
Implementação prática
Levando em conta este cenário vamos implementar o padrão Visitor usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.
A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:
Aqui fizemos o seguinte:
A seguir temos o código usado na implementação:
1- A interface IVisitor
//Visitor public interface IVisitor { void Accept(Carro carro); } |
2- A Interface ILoja
//Element public interface ILoja { void Visit(IVisitor visitor); } |
3- Classe PrecoVisitor
using System; namespace ExemploVisitor1 { //ConcreteVisitor public class PrecoVisitor : IVisitor { private const int CARRO_DESCONTO =12; public void Accept(Carro carro)
{
decimal precoCarroAposDesconto = carro.Preco
- ((carro.Preco / 100) * CARRO_DESCONTO);
Console.WriteLine($"{carro.Modelo} {carro.Nome} :" +
$" ${precoCarroAposDesconto} ");
}
}
}
|
4- Classe Carro
//ConcreteElement public class Carro : ILoja { public string Nome { get; set; } public decimal Preco { get; set; } public string Modelo { get; set; } public void Visit(IVisitor visitor)
{
visitor.Accept(this);
}
}
|
7- Program
|
A execução do projeto irá apresentar o seguinte resultado:
Temos assim um exemplo básico de implementação do padrão Visitor.
Este padrão pode ser usado junto com o padrão Iterator para percorrer uma estrutura de dados complexas e executar alguma operação sobre os seus elementos.
Pegue o código do projeto aqui : ExemploVisitor1.zip
"Deus tenha
misericórdia de nós e nos abençoe; e faça resplandecer o seu rosto sobre nós
(Selá.)
Para que se conheça na terra o teu caminho, e entre todas as nações a tua
salvação."
Salmos 67:1,2
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository