[Delphi] Design Patterns GoF – Builder

[Delphi] Design Patterns - Builder

Alô, leitores!
Em continuidade aos artigos sobre Design Patterns, o artigo de hoje abordará o Builder, um padrão de projeto da mesma categoria do Abstract Factory, chamada “criacionais” (ou “de criação”). Recebem este nome por serem padrões que envolvem a criação de objetos em diferentes contextos. Vamos conhecer a proposta do Builder?

 

Se procurarmos pela definição de “Builder” na internet, encontraremos o seguinte:

É um padrão de projeto de software que permite a separação da construção de um objeto complexo da sua representação, de forma que o mesmo processo de construção possa criar diferentes representações.

Ao procuramos mais detalhes, descobriremos que existem os seguintes elementos neste padrão: Director, Builder, Concrete Builder e Product.

 

André, o que significa tudo isso?
Bom, vocês sabem que um dos maiores objetivos do blog é descomplicar conceitos, não é? Para isso, costumo utilizar analogias para facilitar o entendimento. Neste artigo, não será diferente. Vou apresentar-lhes um exemplo que já é relativamente clássico com o Builder: uma empresa de fast-food. Assim os elementos do Builder ficarão mais claros.
Imagine que você esteja em um fast-food, como McDonalds, Burger King ou Bob’s. Você se dirige ao balcão e pede o lanche X ao atendente. Este, por sua vez, repassa o pedido para a cozinha, onde o lanche é produzido. Quando pronto, o lanche é entregue ao atendente e, depois, a você. Nesse pequeno cenário, observe o seguinte:

  • O atendente é apenas um intermediário entre o cliente e a cozinha;
  • O atendente não conhece como o lanche é feito. Apenas repassa o pedido do cliente;
  • O lanche é feito pelos funcionários da cozinha, enquanto o atendente apenas o espera pronto.

Opa, já temos alguns parâmetros para comparar o cenário acima com o Builder:

  • O atendente é o Director, que solicita a construção de um objeto (produto);
  • O lanche é o Product, ou seja, o produto propriamente dito;
  • O quadro de funcionários que fazem os lanches é o Builder (Interface);
  • O funcionário que fez o seu lanche é o Concrete Builder (objeto).

Dessa forma, podemos simplificar a definição do Builder:

É um padrão de projeto que permite encapsular a lógica de construção de objetos, de modo que diferentes representações possam ser criadas a partir de uma mesma interface.

 

Por quê encapsular?
O encapsulamento refere-se ao fato do Director (atendente) não conhecer como o produto é fabricado. Apenas aguarda o produto pronto para ser entregue ao cliente.

O que são representações?
Considere que cada representação é um tipo de lanche que o fast-food vende. 🙂

 

Em palavras gerais, o Builder tecnicamente funciona da seguinte forma: o Director “solicita” um Product para o Concrete Builder, que implementa a Interface Builder. Este é responsável por “construir” o produto – que denominamos de “complexo”, pois pode ser constituído de várias partes – e devolver para o Director que, como disse anteriormente, não participa do processo de “construção”.

Bom, mas vamos ao que interessa. Pode parecer estranho, mas o exemplo prático deste artigo não será sobre o fast-food!
Vamos usar outro cenário, como… hum, deixa eu pensar… já sei! Cestas de produtos! Imagine o seguinte: mensalmente, um supermercado monta diferentes cestas de produtos (básica, completa e especial) para entregar para empresas conveniadas. Porém, para isso, o supermercado precisa manter controle dos produtos que vão em cada cesta, bem como o valor total delas. Opa, um padrão Builder aqui cairá bem!

 

Em primeiro lugar, vamos criar duas Interfaces: uma para a cesta de produtos e outra para o Builder:

ICestaProdutos = interface
  procedure AdicionarProdutoNaCesta(const Produto: string);
  function ObterListaProdutos: string;
  function ObterValorTotalCesta: string;
end;
 
ICestaBuilder = interface
  function MontarCestaBasica: ICestaProdutos;
  function MontarCestaCompleta: ICestaProdutos;
  function MontarCestaEspecial: ICestaProdutos;
end;

 

Agora precisamos implementá-las. A classe TCestaProduto, que implementa ICestaProdutos, terá a seguinte codificação:

type
  TCestaProdutos = class(TInterfacedObject, ICestaProdutos)
  private
    // armazena a lista de produtos da cesta
    ListaProdutos: TStringList;
 
    // armazena o valor total da cesta
    ValorTotalCesta: real;
  public
    constructor Create;
    destructor Destroy; override;
 
    procedure AdicionarProdutoNaCesta(const Produto: string); virtual;
    function ObterListaProdutos: string;
    function ObterValorTotalCesta: string;
  end;
 
...
 
constructor TCestaProdutos.Create;
begin
  ListaProdutos := TStringList.Create;
  ValorTotalCesta := 0;
end;
 
destructor TCestaProdutos.Destroy;
begin
  FreeAndNil(ListaProdutos);
  inherited;
end;
 
procedure TCestaProdutos.AdicionarProdutoNaCesta(const Produto: string);
var
  ValorProduto: real;
begin
  // consulta o valor do produto
  ValorProduto := ConsultarValorProduto(Produto);
 
  // atualiza o valor total da cesta
  ValorTotalCesta := ValorTotalCesta + ValorProduto;
 
  // adiciona o produto na lista de produtos
  ListaProdutos.Add(Format('Produto: %s - %.2f', [Produto, ValorProduto]));
end;
 
function TCestaProdutos.ObterListaProdutos: string;
begin
  result := ListaProdutos.Text;
end;
 
function TCestaProdutos.ObterValorTotalCesta: string;
begin
  result := 'Total: ' + Format('%.2f', [ValorTotalCesta]);
end;

 

Em seguida, codificaremos a implementação da Interface ICestaBuilder, que será o nosso “montador” de cestas:

TCestaBuilder = class(TInterfacedObject, ICestaBuilder)
public
  function MontarCestaBasica: ICestaProdutos;
  function MontarCestaCompleta: ICestaProdutos;
  function MontarCestaEspecial: ICestaProdutos;
end;
 
...
 
function TCestaBuilder.MontarCestaBasica: ICestaProdutos;
var
  Cesta: ICestaProdutos;
begin
  Cesta := TCestaProdutos.Create;
  Cesta.AdicionarProdutoNaCesta('Arroz');
  Cesta.AdicionarProdutoNaCesta('Feijão');
  Cesta.AdicionarProdutoNaCesta('Macarrão');
  Cesta.AdicionarProdutoNaCesta('Óleo');
  result := Cesta;
end;
 
function TCestaBuilder.MontarCestaCompleta: ICestaProdutos;
var
  Cesta: ICestaProdutos;
begin
  Cesta := TCestaProdutos.Create;
  Cesta.AdicionarProdutoNaCesta('Arroz');
  Cesta.AdicionarProdutoNaCesta('Feijão');
  Cesta.AdicionarProdutoNaCesta('Açúcar');
  Cesta.AdicionarProdutoNaCesta('Refrigerante');
  Cesta.AdicionarProdutoNaCesta('Leite');
  result := Cesta;
end;
 
function TCestaBuilder.MontarCestaEspecial: ICestaProdutos;
var
  Cesta: ICestaProdutos;
begin
  Cesta := TCestaProdutos.Create;
  Cesta.AdicionarProdutoNaCesta('Arroz');
  Cesta.AdicionarProdutoNaCesta('Feijão');
  Cesta.AdicionarProdutoNaCesta('Ravioli');
  Cesta.AdicionarProdutoNaCesta('Champanhe');
  Cesta.AdicionarProdutoNaCesta('Panetone');
  result := Cesta;
end;

 

Pronto, pessoal!
O nosso Director (classe que vai utilizar o Builder para montar as cestas), neste caso, será a própria aplicação – ou o usuário, se você preferir. Portanto, crie um formulário e adicione um componente de escolha de opção (como TComboBox ou TRadioGroup) que tenha as seguintes opções: Cesta Básica, Cesta Completa e Cesta Especial. Nós vamos acessar o ItemIndex deste componente para descobrir qual tipo de cesta o usuário selecionou. No exemplo a seguir, optei pelo TComboBox.
Adicione também um TMemo, que receberá as informações da cesta, e também um botão, que executará a operação (chamada do Builder) através do código:

procedure TForm1.Button1Click(Sender: TObject);
var
  Builder: ICestaBuilder;
  Cesta: ICestaProdutos;
begin
  // cria o construtor (Builder) e solicita a "construção" do produto
  Builder := TCestaBuilder.Create;
 
  // "monta" a cesta (Product)
  case ComboBox1.ItemIndex of
    0: Cesta := Builder.MontarCestaBasica;
    1: Cesta := Builder.MontarCestaCompleta;
    2: Cesta := Builder.MontarCestaEspecial;
  end;
 
  Memo1.Clear;
 
  // exibe a lista de produtos da cesta no Memo
  Memo1.Lines.Add(Cesta.ObterListaProdutos);
 
  // exibe o valor total da cesta no Memo
  Memo1.Lines.Add(Cesta.ObterValorTotalCesta);
end;

Observe como o código do botão ficou enxuto! Todo o processamento acontece dentro do Builder, e apenas esperamos o resultado.
Imagine então, como seria simples adicionar uma nova cesta de produtos, como “cesta de natal”. Fácil! Criaríamos uma nova classe que implementa ICestaProdutos e “ensinaríamos” o Builder a fabricar o produto, criando o método “MontarCestaNatal”.

Para tirar a prova da eficiência deste padrão, experimente implementar esse mesmo exemplo sem utilizá-lo. Você irá notar que, em pouco tempo, começará a surgir algumas linhas duplicadas…

 

André, só uma última dúvida: onde está a implementação do método “ConsultarValorProduto”?
Vou deixar essa implementação como tarefa de casa! 🙂

 

Não compreendeu muito bem? Sem problemas! Clique no link a seguir para baixar o exemplo completo do Builder:

Exemplo de Builder com Delphi

Grande abraço, pessoal!


 

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

2 comentários

  1. Oi André,

    muito legal ler seu conteúdo sobre Delphi, parabéns!
    Mantenho um projeto chamado /dev/All, que é um agregador de blogs de desenvolvedores voltado para a comunidade. Nosso endereço é http://www.devall.com.br

    Adoraríamos ver seu conteúdo listado entre os posts do nosso agregador. Basta clicar em “Cadastre seu blog”.

    Mais uma vez, parabéns pelo conteúdo!

    1. Boa noite, Henrique!

      Agradeço fortemente pela visita e também pelo convite para cadastrar o blog no agregador!
      Vou fazer o cadastro o mais breve possível. Será um grande prazer!
      Parabéns pela iniciativa, Henrique. Um agregador como esse é muito importante para a comunidade de programadores.

      Um grande abraço! 🙂

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.