À 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
Trueou o0representam 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
.Buildatua 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 := Selfpermite 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étodoBuild: ao definirFVenda := nilapós oResult, 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á umTry..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
TPersistentdevem 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.
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.