Um dos propósitos que nos leva a agruparmos objetos numa mesma estrutura é facilitar a execução de operações envolvendo as partes que constituem esse objeto. Ou seja, operações nas quais percorreremos todos os elementos e aplicaremos a cada um deles a operação desejada. Nesse processo, a existência de objetos de tipos distintos deve ser levada em conta porque uma mesma operação pode ter significado diferente, dependente do tipo de objeto. Isto nos leva a ter que escolher a versão correta da operação que deve ser executada em cada elemento. Por exemplo, considere que você tem uma classe representando a lista de recursos necessários para fabricar um produto. A lista inclui as matérias primas, as máquinas envolvidas e até roteiros descrevendo o processo de fabricação. Uma operação que imprima a estrutura terá que considerar as diferenças entre cada uma dessas partes pois elas têm informações específicas distintas.
Em princípio, parece que uma herança simples pode resolver a questão. Mas, e se for necessário acrescentar novos tipos de operação, com nomes e parâmetros diferentes - salvar em formato XML, por exemplo ? Teríamos que alterar a estrutura da agregação para que ela comporte a nova operação.
O intuito do Design Pattern comportamental Visitor é facilitar a manutenção desse tipo de estrutura heterogênea. Ele nos obriga a manter separada a estrutura de um objeto e os algoritmos das operações que podem ser aplicadas sobre seus componentes. Com isso, podemos introduzir novas operações à estrutura sem que seja preciso modificar as classes onde essas operações serão aplicadas. Em outras palavras, o que o Visitor propõe é transferir a implementação das operações, removendo-as das classes que compõem a agregação e passando-as para classes próprias. Com isso, preserva-se a estrutura original tanto da agregação quanto das partes que a compõem.
O diagrama abaixo mostra um exemplo prático da aplicabilidade desse padrão. Nele, um Produto Acabado é composto de uma lista de recursos, incluindo as matérias primas, máquinas e instruções para a fabricação dele. São definidas duas operações externas : uma para exportar a arquitetura do produto para XML e outra para imprimí-la.
De acordo com o papel que exercem na implementação de uma solução para o padrão Visitor, as classes envolvidas são formalmente conhecidas pelos nomes que seguem:
O Visitor é uma abstração que define quais classes de objetos poderão ser submetidas às operações que serão disponibilizadas. Ele faz isso ao introduzir métodos sobrecarregados, um para cada tipo de elemento da agregação que sofrerá a operação. Por convenção, este método é nomeado Visit. No diagrama, este é o papel da classe TWOperacoesPA.
A implementação das operações em si é feita em classes chamadas Concrete Visitor. Essas classes são heranças simples do Visitor, o que significa que elas terão que fornecer sua própria versão de cada método Visit definido na classe base. Com isso, a operação tratará de forma condizente os diferentes componentes da coleção. No exemplo, as classes TWRecursoImprimir e TWRecursoSaveToXml fazem esse papel.
Chamamos de Visitable (ou Element) a classe que define um método Accept capaz de receber uma instância da classe de operação e executar a versão correta do método Visit dessa operação. Ela, portanto, serve de ponto de entrada da execução, definindo o comportamento esperado para todas as heranças que poderão ser "visitadas". São às vezes chamadas de Element por coincidir com a classe base dos elementos que compõem uma agregação. A classe que exerce esse papel no diagrama de exemplo é a TWRecursoProducao.
Os Concrete Visitable (ou Concrete Element) são as classes que efetivamente implementam o método Accept, direcionando a execução da operação para o tipo de objeto que representam. Isto é, chamando o método Visit correto da operação passada como parâmetro. As classes TWMateriaPrima, TWMaquina e TWRoteiro são exemplos disso no diagrama.
O Object Structure é a classe que contém a coleção de elementos sobre os quais uma operação será executada. Ela, então, terá que prover um meio para que a coleção seja percorrida de modo que todos os seus componentes possam ser submetidos à operação desejada. Este é o papel da classe TWProdutoAcabado no diagrama.
O Client é responsável por manter a instância tanto do Object Structure quanto da operação que será executada. Ela também navega os elementos da estrutura, executando em cada um a operação solicitada.
A implementação das operações em si é feita em classes chamadas Concrete Visitor. Essas classes são heranças simples do Visitor, o que significa que elas terão que fornecer sua própria versão de cada método Visit definido na classe base. Com isso, a operação tratará de forma condizente os diferentes componentes da coleção. No exemplo, as classes TWRecursoImprimir e TWRecursoSaveToXml fazem esse papel.
Chamamos de Visitable (ou Element) a classe que define um método Accept capaz de receber uma instância da classe de operação e executar a versão correta do método Visit dessa operação. Ela, portanto, serve de ponto de entrada da execução, definindo o comportamento esperado para todas as heranças que poderão ser "visitadas". São às vezes chamadas de Element por coincidir com a classe base dos elementos que compõem uma agregação. A classe que exerce esse papel no diagrama de exemplo é a TWRecursoProducao.
Os Concrete Visitable (ou Concrete Element) são as classes que efetivamente implementam o método Accept, direcionando a execução da operação para o tipo de objeto que representam. Isto é, chamando o método Visit correto da operação passada como parâmetro. As classes TWMateriaPrima, TWMaquina e TWRoteiro são exemplos disso no diagrama.
O Object Structure é a classe que contém a coleção de elementos sobre os quais uma operação será executada. Ela, então, terá que prover um meio para que a coleção seja percorrida de modo que todos os seus componentes possam ser submetidos à operação desejada. Este é o papel da classe TWProdutoAcabado no diagrama.
O Client é responsável por manter a instância tanto do Object Structure quanto da operação que será executada. Ela também navega os elementos da estrutura, executando em cada um a operação solicitada.
O padrão Iterator também é projetado para permitir a navegação entre elementos que compõem um objeto. A diferença é que no Iterator os elementos devem ser herança de uma mesma classe enquanto no Visitor isso não é obrigatório.
O cenário onde o padrão Visitor é aplicável se assemelha ao uso de interfaces, já que em ambos os casos, uma estrutura externa estabelece as operações que a classe é capaz de executar. No entanto, interfaces forçam a implementação das operações dentro da classe ao invés de fazê-lo em classes separadas, como no Visitor. Obviamente, ambos os recursos podem ser utilizados em conjunto para produzir uma solução mais flexível.
Alguns críticos alegam que o Visitor fere o encapsulamento das classes ao obrigar que parte de seu funcionamento seja delegada a outra classe. Mas, podemos encarar essa classe extra como uma operação distinta, minimizando ou até mesmo eliminando o efeito da quebra de encapsulamento.
No próximo post eu mostro uma sugestão de implementação desse pattern usando o Delphi.
Nenhum comentário :
Postar um comentário
OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.
Observação: somente um membro deste blog pode postar um comentário.