[Delphi] Design Patterns GoF – Builder

[Delphi] Design Patterns - Builder

Alô, leitores!
Em continuidade aos artigos sobre Design Patterns, estudaremos 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 deste padrão de projeto?

 

Se pesquisarmos por “Builder Pattern” na internet, encontraremos a seguinte definição:

É 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 ainda que existem quatro elementos neste padrão, chamados de 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. 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 para esclarecer o papel de cada elemento: uma empresa de fast-food.
Imagine que você esteja em um fast-food e que tenha pedido um lanche ao atendente. Este, por sua vez, repassa o pedido para a cozinha, onde o lanche é produzido. Quando pronto, o lanche é entregue ao atendente e, por fim, a você. Nesse pequeno cenário, observa-se:

  • 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.

Traduzindo em termos técnicos, é possível ligar estes pontos aos elementos do Builder:

  • O atendente é o Director, que solicita a construção de um objeto (produto);
  • O lanche é o Product, ou seja, o produto resultante da construção;
  • O quadro de funcionários que fazem os lanches é o Builder (Interface);
  • O funcionário que preparou o lanche pedido é 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. Este é responsável por “construir” o produto – no qual 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”.

Acredito que o conceito ficará ainda mais claro com um exemplo prático. Neste artigo, codificaremos a emissão de um Relatório de Fornecedores em HTML.

Por quê?
Sabemos que a maioria dos relatórios são divididos em diferentes seções para facilitar a interpretação, certo? O objeto que representa um relatório, portanto, pode ser considerado como “complexo”, pois consiste de várias partes que podem ser condicionadas por regras de negócio. Neste exemplo, simularemos essa complexidade por meio de três passos:

  • Construção do cabeçalho
  • Construção do corpo
  • Construção do rodapé

 

No entanto, este relatório não será um qualquer. Utilizaremos Bootstrap para deixá-lo com um visual mais agradável e responsivo.

Em primeiro lugar, definiremos a classe Productque consiste no produto que será “construído”. No nosso caso, será uma página HTML:

type
  { Product }
  TProduct = class
  private
    // Objeto que armazenará o conteúdo HTML
    FConteudoHTML: TStringList;
  public
    constructor Create;
    destructor Destroy; override;
 
    // Concatena o conteúdo HTML ao objeto "FConteudoHTML"
    procedure Adicionar(const Texto: string);
 
    // Abre uma caixa de diálogo para salvar o arquivo HTML
    procedure SalvarArquivo;
  end;
 
implementation
 
uses
  System.SysUtils, Vcl.Dialogs;
 
{ TProduct }
 
procedure TProduct.Adicionar(const Texto: string);
begin
  FConteudoHTML.Add(Texto);
end;
 
constructor TProduct.Create;
begin
  FConteudoHTML := TStringList.Create;
end;
 
destructor TProduct.Destroy;
begin
  FreeAndNil(FConteudoHTML);
  inherited;
end;
 
procedure TProduct.SalvarArquivo;
var
  SaveDialog: TSaveDialog;
begin
  SaveDialog := TSaveDialog.Create(nil);
  try
    SaveDialog.Execute;
    FConteudoHTML.SaveToFile(SaveDialog.FileName);
  finally
    FreeAndNil(SaveDialog);
  end;
end;

O método “Adicionar” será o mais utilizado dessa classe, já que tem a função de concatenar as tags HTML para montagem da página.

Em segundo lugar, codificaremos a Interface Builder, que declara a sequência de passos para construir o produto:

uses
  Pattern.Product;
 
type
  { Builder }
  IBuilder = interface
 
    // Métodos para criar as "partes" do objeto complexo
    procedure BuildCabecalho;
    procedure BuildDetalhes;
    procedure BuildRodape;
 
    // Função que retorna o "produto" pronto
    function GetRelatorio: TProduct;
  end;

 

Em seguida, precisamos de uma implementação concreta da Interface acima. Tal classe, que recebe o nome de Concrete Builder, será responsável por implementar cada passo da construção. No nosso exemplo, preencheremos as tags HTML referente à cada seção da página, fazendo referência aos templates do Bootstrap. Embora o código pareça um pouco extenso, a maior parte resume-se a comandos HTML.

uses
  Pattern.Builder, Pattern.Product;
 
type
  { Concrete Builder }
  TConcreteBuilder = class(TInterfacedObject, IBuilder)
  private
    FProduct: TProduct;
    FDados: olevariant;
    FQtdeRegistros: integer;
  public
    constructor Create(Dados: olevariant);
    destructor Destroy; override;
 
    // Métodos para criar as "partes" do objeto complexo
    procedure BuildCabecalho;
    procedure BuildDetalhes;
    procedure BuildRodape;
 
    // Função que retorna o "produto" pronto
    function GetRelatorio: TProduct;
  end;
 
implementation
 
uses
  System.SysUtils, DataSnap.DBClient;
 
{ TConcreteBuilder }
 
procedure TConcreteBuilder.BuildCabecalho;
begin
  // Preenche o conteúdo HTML referente ao cabeçalho
 
  FProduct.Adicionar('<html><head><meta charset="UTF-8">');
 
  // Importação das bibliotecas do Bootstrap
  FProduct.Adicionar('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">');
  FProduct.Adicionar('<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>');
 
  FProduct.Adicionar('</head>');
  FProduct.Adicionar('<div class="container">');
  FProduct.Adicionar('<div class="panel panel-default">');
  FProduct.Adicionar('<div class="panel-heading">');
  FProduct.Adicionar('<h4>Relatório de Fornecedores</h4>');
  FProduct.Adicionar('<h4>' + FormatDateTime('dd/mm/yyyy hh:nn:ss', Now) + '</h4>');
  FProduct.Adicionar('</div>');
end;
 
procedure TConcreteBuilder.BuildDetalhes;
var
  DataSet: TClientDataSet;
begin
  // Preenche o conteúdo HTML referente à tabela de registros
  FProduct.Adicionar('<table class="table table-fixed">');
  FProduct.Adicionar('<thead><tr>');
  FProduct.Adicionar('<th class="col-xs-2">Código</th>');
  FProduct.Adicionar('<th class="col-xs-8">Fornecedor</th>');
  FProduct.Adicionar('<th class="col-xs-2">Cidade</th>');
  FProduct.Adicionar('</tr></thead><tbody>');
 
  DataSet := TClientDataSet.Create(nil);
  try
    DataSet.Data := FDados;
    DataSet.First;
 
    // Para cada iteração nos registros do DataSet, criauma linha no HTML
    while not DataSet.Eof do
    begin
      FProduct.Adicionar('<tr>');
      FProduct.Adicionar('<td class="col-xs-2">' + DataSet.FieldByName('VendorNo').AsString + '</td>');
      FProduct.Adicionar('<td class="col-xs-2">' + DataSet.FieldByName('VendorName').AsString + '</td>');
      FProduct.Adicionar('<td class="col-xs-2">' + DataSet.FieldByName('City').AsString + '</td>');
      FProduct.Adicionar('</tr>');
 
      // Incrementa a variável de quantidade de registros
      Inc(FQtdeRegistros);
 
      DataSet.Next;
    end;
 
    // Fecha o HTML da tabela
    FProduct.Adicionar('</tbody></table></div></div>');
  finally
    FreeAndNil(DataSet);
  end;
end;
 
procedure TConcreteBuilder.BuildRodape;
begin
  // Preenche o conteúdo HTML referente ao rodapé
  FProduct.Adicionar('<div id="footer">');
  FProduct.Adicionar('<div class="container">');
  FProduct.Adicionar('<p class="text-center">Qtde de Registros: ' + IntToStr(FQtdeRegistros)+ '</p>');
  FProduct.Adicionar('</div></div></body></html>');
end;
 
constructor TConcreteBuilder.Create(Dados: olevariant);
begin
  FQtdeRegistros := 0;
 
  // Recebe os dados do formulário como parâmetro
  FDados := Dados;
 
  // Cria a instância do Product
  FProduct := TProduct.Create;
end;
 
destructor TConcreteBuilder.Destroy;
begin
  // Libera a instância do Product
  FreeAndNil(FProduct);
  inherited;
end;
 
function TConcreteBuilder.GetRelatorio: TProduct;
begin
  // Devolve o produto pronto que, neste caso, é uma página HTML
  result := FProduct;
end;

 

Não tem segredo, não é?
O último elemento, chamado Director, tem apenas a função de receber um Concrete Builder como parâmetro para executar a sequência de passos:

type
  { Director }
  TDirector = class
  public
    // Método responsável por construir o objeto complexo por partes
    procedure Construct(Builder: IBuilder);
  end;
 
implementation
 
{ TDirector }
 
procedure TDirector.Construct(Builder: IBuilder);
begin
  // Sequência de passos para construir o objeto complexo
  Builder.BuildCabecalho;
  Builder.BuildDetalhes;
  Builder.BuildRodape;
end;

 

Recapitulando então, pessoal: tudo se inicia pelo Director, que equivale ao atendente na analogia no começo deste artigo. O Director recebe um Concrete Builder como parâmetro para executar a sequência de passos. No final deste processo, recebemos um Product “construído”.

Para testar o padrão de projeto, elaborei um formulário simples, no qual carrega um arquivo de dados chamado “vendors.xml” que acompanha a instalação do RAD Studio.

Formulário de exemplo para demonstrar o Design Pattern Builder

 

No botão de geração do relatório, atente-se à codificação:

uses
  Pattern.Director, Pattern.Builder, Pattern.ConcreteBuilder, Pattern.Product;
 
{...}
 
var
  Director: TDirector;
  ConcreteBuilder: IBuilder;
  Product: TProduct;
begin
  // Cria uma instância do Director
  Director := TDirector.Create;
 
  // Cria uma instância do Concrete Builder, informando os dados como parâmetro
  ConcreteBuilder := TConcreteBuilder.Create(ClientDataSet.Data);
  try
    // Solicita a construção do objeto (relatório) ao Director
    Director.Construct(ConcreteBuilder);
 
    // Recebe o produto pronto (constrúido)
    Product := ConcreteBuilder.GetRelatorio;
 
    // Chama o método para salvar o arquivo em disco
    Product.SalvarArquivo;
  finally
    // Libera o Director da memória
    FreeAndNil(Director);
  end;
end;

 

A título de conhecimento, vale destacar que o Concrete Builder não precisa ser liberado da memória, já que foi declarado como Interface e o próprio Delphi se encarrega da liberação por meio de um mecanismo de contagem de referência.

Pois bem, este é o resultado:

Exemplo de relatório gerado em HTML

 

Ou melhor, clique no link abaixo para acessar a página gerada:

Exemplo de Relatório em HTML gerado com o Design Pattern Builder

 

Ainda fiquei com uma dúvida: por que existe a Interface Builder?
O propósito do padrão de projeto Builder não limita-se a apenas construir objetos complexos. A sua proposta, além disso, é permitir que diferentes representações (ou seja, diferentes produtos) possam ser construídos com a mesma sequência de passos.
Considere, por exemplo, que seja necessário gerar um relatório em PDF com estes mesmos dados. Teríamos apenas que criar um novo Product e um novo Concrete Builder encarregado de construir o cabeçalho, corpo e rodapé neste formato, provavelmente utilizando o QuickReport, FastReport ou Rave Reports. No formulário, basta indicar o Concrete Builder desejado como parâmetro do Director. O resto ocorre naturalmente. =D

 

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

Exemplo de Builder com Delphi

Ou no GitHub:

https://github.com/AndreLuisCelestino/Delphi-DesignPatterns/tree/master/Builder-AndreCelestino

 

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.