Tecnologia além do código.
Régys Borges da Silveira

Design Patterns em Delphi 13: Builder e Prototype

Neste artigo 9 tópicos

À medida que nossas aplicações crescem, a complexidade dos nossos objetos aumenta. Você já se deparou com um construtor que exige 15 parâmetros, onde metade deles são opcionais ou nil? Ou precisou duplicar um objeto complexo que levou segundos para ser configurado e percebeu que o Delphi não oferece um “Clone” nativo por padrão?

Neste artigo, vamos explorar como os padrões Builder e Prototype no Delphi 13 resolvem a criação de objetos complexos e a clonagem eficiente, garantindo um código limpo e sem efeitos colaterais.

1. O Problema dos Construtores Gigantes

O Modo Antigo: Parâmetros Infinitos

No desenvolvimento legado, é comum encontrarmos classes com construtores sobrecarregados ou com uma lista interminável de argumentos.

// MODO LEGADO - Difícil de ler e manter
Venda := TVenda.Create(Cliente, Vendedor, ListaItens, 10, True, 'Observação', Nil, 0);

Por que isso prejudica o projeto?

  • Ilegibilidade: Quem lê o código não sabe o que o True ou o 0 representam sem consultar a definição da classe.
  • Fragilidade: Se você precisar adicionar um novo parâmetro no meio, quebrará centenas de chamadas em todo o sistema.
  • Estado Inválido: O objeto pode ser instanciado em um estado incompleto se o desenvolvedor esquecer de configurar uma propriedade essencial após o Create.

2. Builder: Construção Passo a Passo com Fluent Interface

O padrão Builder é a solução definitiva para a criação de objetos complexos. No Delphi 13, implementamos este padrão para que a lógica de validação e montagem fique encapsulada, protegendo a classe de negócio contra estados inválidos.

Implementação Completa: O Builder de Vendas

Abaixo, demonstramos uma implementação profissional onde o TVendaBuilder gerencia a criação de uma TVenda (objeto de domínio), garantindo que os itens e o cliente sejam configurados corretamente antes da instância final ser entregue.

unit Regys.Patterns.Builder;

interface

uses
  System.SysUtils, System.Generics.Collections;

type
  // Objeto de Domínio (O que queremos construir)
  TVenda = class
  public
    Cliente: string;
    Itens: TList<string>;
    Desconto: Currency;
    constructor Create;
    destructor Destroy; override;
  end;

  // Interface do Builder para permitir Fluent Interface e gerenciamento ARC
  IVendaBuilder = interface
    ['{A1B2C3D4-E5F6-4789-B0C1-D2E3F4A5B6C7}']
    function ComCliente(const ANome: string): IVendaBuilder;
    function ComItem(const AProduto: string; AQtd: Double): IVendaBuilder;
    function ComDesconto(AValor: Currency): IVendaBuilder;
    function Build: TVenda;
  end;

  // Implementação do Builder
  TVendaBuilder = class(TInterfacedObject, IVendaBuilder)
  private
    FVenda: TVenda;
    constructor Create;
  public
    class function New: IVendaBuilder;
    destructor Destroy; override;
    
    function ComCliente(const ANome: string): IVendaBuilder;
    function ComItem(const AProduto: string; AQtd: Double): IVendaBuilder;
    function ComDesconto(AValor: Currency): IVendaBuilder;
    function Build: TVenda;
  end;

implementation

{ TVenda }

constructor TVenda.Create;
begin
  Itens := TList<string>.Create;
end;

destructor TVenda.Destroy;
begin
  Itens.Free;
  inherited;
end;

{ TVendaBuilder }

constructor TVendaBuilder.Create;
begin
  FVenda := TVenda.Create;
end;

destructor TVendaBuilder.Destroy;
begin
  if Assigned(FVenda) then FVenda.Free; // Caso o Build não tenha sido chamado
  inherited;
end;

class function TVendaBuilder.New: IVendaBuilder;
begin
  Result := Self.Create;
end;

function TVendaBuilder.ComCliente(const ANome: string): IVendaBuilder;
begin
  FVenda.Cliente := ANome;
  Result := Self;
end;

function TVendaBuilder.ComItem(const AProduto: string; AQtd: Double): IVendaBuilder;
begin
  FVenda.Itens.Add(Format('%g x %s', [AQtd, AProduto]));
  Result := Self;
end;

function TVendaBuilder.ComDesconto(AValor: Currency): IVendaBuilder;
begin
  FVenda.Desconto := AValor;
  Result := Self;
end;

function TVendaBuilder.Build: TVenda;
begin
  // Espaço para validações de negócio antes de entregar o objeto
  if FVenda.Cliente.IsEmpty then
    raise Exception.Create('Não é possível construir uma venda sem cliente.');
    
  if FVenda.Itens.Count = 0 then
    raise Exception.Create('A venda deve possuir ao menos um item.');

  Result := FVenda;
  FVenda := nil; // "Solta" a referência para que o Builder não destrua o objeto
end;

end.

Por que esta implementação é Superior?

  • Desacoplamento: O código que consome a venda não precisa saber como instanciar TList<string> ou inicializar campos complexos; ele apenas descreve o que quer.
  • Segurança de Estado: O método .Build atua como um Gatekeeper. Ele garante que as regras de negócio (como “cliente obrigatório”) sejam respeitadas, algo que um construtor simples não consegue forçar de forma tão limpa.
  • Encadeamento (Chaining): O uso de Result := Self permite o estilo de escrita “fluente”, que reduz drasticamente a poluição visual do código.
  • Gestão de Memória no Delphi 13: Ao usar IVendaBuilder (Interface), o objeto Builder é destruído automaticamente assim que sai de escopo. Note o truque técnico no método Build: ao definir FVenda := nil após o Result, garantimos que o Builder não destrua a nossa venda recém-criada ao ser limpo da memória.

3. Prototype: Clonagem Eficiente e o Problema da Cópia Rasa

O padrão Prototype é utilizado quando a criação de uma nova instância é mais cara ou complexa do que copiar uma existente. No Delphi, o comando Assign muitas vezes não é suficiente.

Deep Copy no Delphi 13

No Delphi 13, para implementar um Prototype real, precisamos lidar com a Deep Copy (Cópia Profunda), garantindo que listas e objetos internos também sejam duplicados, evitando que dois objetos apontem para o mesmo endereço de memória.

type
  IPrototype<T> = interface
    function Clone: T;
  end;

{ TConfiguracaoComplexa }
function TConfiguracaoComplexa.Clone: TConfiguracaoComplexa;
begin
  Result := TConfiguracaoComplexa.Create;
  Result.FPropriedadeSimples := Self.FPropriedadeSimples;
  // Deep Copy de listas usando Generics no Delphi 13
  for var LItem in Self.FListaItens do
    Result.FListaItens.Add(LItem.Clone); 
end;

Aplicação Prática: Imagine um sistema de relatórios onde o usuário configura filtros complexos. Em vez de reprocessar tudo, você “clona” o estado atual para gerar uma variação do relatório em uma nova aba.

4. Gerenciamento de Memória e Fluência no Delphi 13

Ao aplicar Builder e Prototype no Delphi 13 (Athens), devemos estar atentos ao ciclo de vida:

  • Builders e Interfaces: Se o seu Builder retornar interfaces, o gerenciamento de memória do “construidor” é automático. No entanto, o objeto final (Build) costuma ser uma classe de negócio que exigirá um Try..Finally.
  • Performance do Prototype: Clonar objetos que possuem muitas referências a strings ou arrays dinâmicos no Delphi 13 é extremamente rápido devido ao Copy-on-Write da linguagem, mas objetos que herdam de TPersistent devem ser tratados com cautela para não gerar memory leaks em listas internas.
  • Uso de Generics: No Delphi 13, utilize TBuilder<T> para criar fábricas de construção genéricas, reduzindo a repetição de código em sistemas de larga escala.

Referências

FLICK, Stefan. Design Patterns with Delphi: Build enterprise-grade applications with modern Delphi and the right design patterns. 1. ed. Birmingham: Packt Publishing, 2024.

GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos. 1. ed. Porto Alegre: Bookman, 2000.

NOGUEIRA, Rodrigo. Delphi e Clean Architecture: Princípios e práticas para software escalável. 2. ed. São Paulo: Editora Engenharia de Software, 2025.

TEIXEIRA, Marcello. Delphi High Performance: Build fast Delphi applications using concurrency, parallel programming and memory management. 1. ed. Birmingham: Packt Publishing, 2018.

Compartilhar por e-mail

0 comentário(s)

Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Um próximo passo para cada interesse.

Continue estudando com os livros, acompanhe as videoaulas ou consulte as soluções para o seu projeto.

Descubra mais sobre Régys Borges da Silveira

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo