[Delphi] Design Patterns – Composite

[Delphi] Design Patterns - 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 muito útil para executar operações em um conjunto de objetos de forma única.

 

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:

Maquina.ExecutarTrabalho;

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:

var
  Maquina: TMaquina;
begin
  // faz um loop em todas as máquinas que estão em uma "lista"
  for Maquina in ListaMaquinas do
  begin
    // executa a operação da máquina atual
    Maquina.ExecutarTrabalho;
  end;
end;

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.

 

Por qual motivo eu executaria a operação em um objeto “parte”?
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?
Bom, leitores, para a nossa aplicação prática, pensei em apresentar algo sobre…

 

Indústria de móveis!
Não! Agência de turismo! Te peguei, hein? =)
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! \o/
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!

 

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

type
  { Component }
  IViagem = interface
 
    { Operation }
    function CalcularValor: double;
  end;

A classe de viagem será o nosso Leaf…

type
  { Leaf }
  TViagem = class(TInterfacedObject, IViagem)
  private
    Origem: string;
    Destino: string;
    Data: TDateTime;
  public
    procedure DefinirOrigem(const Cidade: string);
    procedure DefinirDestino(const Cidade: string);
    procedure DefinirData(const Data: TDateTime);
 
    { Operation }
    function CalcularValor: double;
  end;

… que terá as seguintes implementações:

procedure TViagem.DefinirDestino(const Cidade: string);
begin
  Self.Destino := Cidade;
end;
 
procedure TViagem.DefinirOrigem(const Cidade: string);
begin
  Self.Origem := Cidade;
end;
 
procedure TViagem.DefinirData(const Data: TDateTime);
begin
  Self.Data := Data;
end;
 
function TViagem.CalcularValor: double;
begin
  result := ConsultarValorViagem(Origem, Destino, Data);
end;

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.

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

type
  { Composite }
  TPacoteViagem = class(TInterfacedObject, IViagem)
  private
    // lista de objetos para armazenar as viagens (Leaf) do pacote
    Viagens: TObjectList<TViagem>;
  public
    constructor Create;
 
    { Operation }
    function CalcularValor: double;
 
    procedure AdicionarViagem(Viagem: TViagem);
  end;

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”:

procedure TPacoteViagem.AdicionarViagem(Viagem: TViagem);
begin
  Viagens.Add(Viagem);
end;

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

function TPacoteViagem.CalcularValor: double;
var
  Viagem: TViagem;
begin
  // Este é o método principal (Operation) que dá propósito ao padrão Composite.
  // O método irá ler cada uma das viagens dentro do pacote,
  // ou seja, cada Leaf dentro do Composite,
  // para calcular o valor de cada viagem, e por fim, obter o valor total do pacote
 
  result := 0;
  for Viagem in Viagens do
  begin
    // Chama o Operation do Leaf
    result := result + Viagem.CalcularValor;
  end;
end;
 

 

Agora o conceito ficou bem mais claro, nã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 suficiente:

var
  Viagem: TViagem;
begin
  Viagem := TViagem.Create;
  try
    Viagem.DefinirOrigem('São Paulo');
    Viagem.DefinirDestino('Rio de Janeiro');
    Viagem.DefinirData('07/11/2016');
    // chama o Operation
    ShowMessage('Total: ' + FloatToStr(Viagem.CalcularValor));
  finally
    FreeAndNil(Viagem);
  end;
end;

 

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:

var
  Pacote: TPacoteViagem;
  Viagem1: TViagem;
  Viagem2: TViagem;  
begin
  Pacote := TPacoteViagem.Create;
  try  
    // primeira viagem do pacote
    Viagem1 := TViagem.Create;
    Viagem1.DefinirOrigem('São Paulo');
    Viagem1.DefinirDestino('Rio de Janeiro');
    Viagem1.DefinirData('07/11/2016');
 
    // segunda viagem do pacote
    Viagem2 := TViagem.Create;
    Viagem2.DefinirOrigem('Rio de Janeiro');
    Viagem2.DefinirDestino('Curitiba');
    Viagem2.DefinirData('10/11/2016');    
 
    Pacote.AdicionarViagem(Viagem1);
    Pacote.AdicionarViagem(Viagem2);
 
    // O método irá ler cada uma das viagens dentro do pacote,
    // ou seja, cada Leaf dentro do Composite,
    // para calcular o valor de cada viagem, e por fim, obter o valor total do pacote
    ShowMessage('Total: ' + FloatToStr(Pacote.CalcularValor));    
  finally
    FreeAndNil(Pacote);
  end;
end;

Feito!

 

André, quando devo utilizar este Design Pattern?
Utilize-o 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.

Exemplo de Composite com Delphi

 

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


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.