[Delphi] Design Patterns GoF – Composite

Olá, pessoal, tudo bem?
Bom, primeiro, preciso justificar a minha demora. Recentemente, alguns projetos paralelos tomaram um pouco do meu tempo, mas, em contrapartida, vocês ouvirão novidades em breve! 😉
Prosseguindo com a nossa série sobre Design Patterns, neste artigo discutiremos sobre o Composite, um padrão de projeto muito útil para executar operações em um conjunto de objetos de forma única.

Introdução

Na ementa da disciplina de Estrutura de Dados nos cursos superiores de TI, ouvimos falar muito em árvores. O conceito, em poucas palavras, significa que uma classe pode ser composta por uma ou várias classes da mesma família, formando uma estrutura de árvore ou uma composição. Imagine, por exemplo, uma linha de produção de móveis que é composta por várias máquinas. Cada máquina executa sua própria operação na peça do móvel (corte, lixa, pintura, acabamento, etc), como se, em programação, o seguinte método fosse chamado:

Porém, cada máquina individual é parte de uma única máquina principal, responsável por mover a peça na esteira. À medida que a peça se move, cada máquina faz o seu trabalho específico e a peça segue adiante para o próximo procedimento. Usando o mesmo raciocínio do código acima, este cenário poderia ser representado dessa forma:

Esse é o propósito chave do Composite. Podemos executar ações tanto no objeto “todo” (que chamará a ação de cada objeto contido na composição), quanto no objeto “parte”. O cliente que chamará essas ações não deve saber com qual está trabalhando, já que ambos devem se comportar da mesma forma.

Motivação do Composite

Na analogia acima, da indústria de móveis, considere que uma das peças teve uma falha de pintura. Neste caso, executaríamos o trabalho apenas da máquina referente à essa etapa, e não do processo como um todo. Veja, então, que ExecutarTrabalho pode ser uma ação de uma máquina individual, como também pode ser uma ação da máquina principal.

Podemos citar também uma analogia de ferramentas (objeto “parte”) e caixa de ferramentas (objeto “todo”). Para algumas operações, precisamos apenas de uma ferramenta específica, como uma chave de fenda. Em outras ocasiões, como o conserto de um veículo, é necessário utilizar um conjunto, ou melhor, uma composição de ferramentas.

Pois bem, para empregarmos o padrão Composite em um projeto, é necessário quatro elementos: Component, Operation, Composite e Leaf.

O Component representa uma Interface que será implementada pelas classes relacionadas à composição, ou seja, pela classe principal e suas “ramificações”. Além disso, essa Interface também deverá conter um método que será comum entre esse objetos, chamado Operation. No exemplo acima, a Interface e o Operation poderiam ser IMaquina e ExecutarTrabalho, respectivamente.

O Composite é a classe concreta de composição que possui uma lista de objetos (ou filhos), comportando-se como uma árvore. O Leaf são as classes filhas, ou as “folhas” da árvore, que estarão atreladas à uma composição.

Sei que, a princípio, todos esses termos parecem complexos, portanto, vou tentar encaixá-los na nossa analogia da indústria de móveis:

  • Component: IMaquina (representa uma abstração das máquinas);
  • Operation: ExecutarTrabalho (operação que tanto a máquina principal quanto cada uma de suas “composições” pode executar);
  • Composite: TMaquinaPrincipal (máquina que leva as peças em uma esteira para as outras máquinas);
  • Leaf: TMaquina (máquinas que realizam trabalhos específicos nas peças).

Dessa forma, ao chamarmos o método ExecutarTrabalho do Composite (máquina principal), este mesmo método será chamado para cada um dos filhos (máquina individual), resultando em um processo completo de fabricação da peça de um móvel. Interessante, não?

Exemplo de codificação do Composite

Bom, leitores, para a nossa aplicação prática, pensei em uma agência de turismo.

Pensei nesse segmento em função do contexto de viagens e pacotes de viagens. Se você já conseguiu identificar que as viagens serão as classes Leaf e o pacote de viagem será a classe Composite, parabéns! 🙂

A ideia é simples: um cliente pode realizar uma única viagem como também pode optar por montar um pacote de viagens que, obviamente, é composto por várias viagens individuais. Sendo assim, podemos considerar que, para qualquer um dos dois, haverá uma consulta de valor, certo? Opa, então esse será o nosso Operation!

Interface Component e método Operation

Para iniciar, é necessário declarar o Component (Interface) e também o Operation (método):

Classe Leaf

A classe de viagem será o nosso Leaf…

… que terá as seguintes implementações:

No código acima, existe uma chamada ao método ConsultarValorViagem que não será abordado do artigo, já que foge do nosso escopo. A responsabilidade deste método é buscar o valor da viagem em um banco de dados ou até mesmo em um WebService, com base na cidade de origem, destino e data da viagem informados como parâmetros.

Classe Composite

A próxima etapa é implementar a classe do Composite:

Com exceção do construtor, observe que há um método diferente, chamado AdicionarViagem, responsável por incluir um objeto do tipo TViagem (Leaf) na lista de objetos Viagens:

Confira agora, com detalhes, como é a implementação do Operation da classe de pacote de viagens:

Agora o conceito ficou bem mais claro, não é?

Em ação!

Para apresentar, na prática, a vantagem deste padrão de projeto, vamos considerar 2 cenários:

1) O cliente da agência de turismo decide realizar uma única viagem.

Não precisamos do Composite. Apenas a chamada do Operation do Leaf já seria o suficiente:

2) O cliente da agência de turismo decide comprar um pacote de viagens.

Neste caso, utilizamos o Composite para formar a “composição” de viagens:

Feito!

Conclusão

Este Design Pattern deve ser utilizado quando o Client (funcionalidade que consumirá o padrão Composite, como uma tela) deve trabalhar com um ou mais métodos (Operations) que sejam comuns entre classes que atuam de forma individual e classes que são compostas de outras classes. Em outras palavras, o Client não deve saber distinguir, por exemplo, se está trabalhando com um ou outro. Deve apenas chamar o Operation e aguardar o resultado. No caso do nosso exemplo prático, o Client aciona o método CalcularValor, independente se é uma viagem ou um pacote de viagens.

Se você ainda estiver com algumas dúvidas sobre o funcionamento do Composite, baixe o projeto de exemplo no link abaixo e execute-o no seu Delphi. Neste projeto, codifiquei uma classe Singleton para armazenar e buscar os valores das viagens a partir de um arquivo XML.

 

Fico por aqui, meus caros!
Abraço e até o próximo artigo!


 

André Celestino