Builder
Builder é um padrão de projeto criacional que ajuda a construir objetos complexos passo a passo, deixando o código:
- mais legível,
- mais flexível para criar variações,
- e menos dependente de construtores gigantes ou de muitos parâmetros opcionais.
Em vez de ter um construtor enorme ou vários construtores sobrecarregados, o Builder permite montar o objeto “em partes”, de forma fluente.
Problema
Seção intitulada “Problema”Imagine que você precisa criar um objeto com muitos atributos, vários deles opcionais ou que têm combinações diferentes:
- Um cadastro de aluno com dados pessoais, endereços, contatos, preferências, permissões, etc.
- Um relatório com dezenas de configurações (formato, filtros, ordenação, colunas, estilos…).
- Um “pedido” em um sistema de e-commerce com vários itens, descontos, frete, cupom, endereço de entrega, etc.
Sem Builder, aparecem alguns problemas comuns:
-
Construtor telescópico (mil parâmetros)
Pedido pedido = new Pedido(cliente,enderecoEntrega,itens,desconto,cupom,frete,observacoes,/* ...e por aí vai... */);- Difícil de ler.
- Fácil passar parâmetros na ordem errada.
- Vira dor de cabeça quando novos campos aparecem.
-
Muitos setters soltos
Pedido pedido = new Pedido();pedido.setCliente(cliente);pedido.setEnderecoEntrega(enderecoEntrega);pedido.setItens(itens);pedido.setDesconto(desconto);// ... vários setters ...- O objeto pode ficar em um estado inconsistente no meio da montagem.
- Fica difícil garantir que tudo necessário foi configurado.
-
Várias combinações de construção
- Às vezes você quer um “pedido simples”.
- Às vezes um “pedido promocional”.
- Às vezes um “pedido corporativo”.
Sem um padrão claro, esse conhecimento de “como montar” se espalha pelo código.
Precisamos de uma forma de organizar a construção de objetos complexos, tornando o processo:
- passo a passo,
- legível,
- reutilizável,
- e com variações controladas.
Solução
Seção intitulada “Solução”O padrão Builder propõe separar o “como construir” do “que está sendo construído”.
As ideias principais são:
-
Produto (Product)
- É o objeto complexo que queremos montar.
- Ex.:
Sanduiche,Pedido,Relatorio.
-
Builder (Interface ou Classe Abstrata)
- Define os passos para montar o produto.
- Ex.:
adicionarPao(),adicionarProteina(),adicionarMolho().
-
Concrete Builders (Builders Concretos)
- Implementam esses passos para montar variações diferentes do produto.
- Ex.:
SanduicheFrangoBuilder,SanduicheVeganoBuilder.
-
Director (Opcional)
- Classe que orquestra a construção, chamando os passos do Builder numa certa ordem.
- Ex.:
Cozinhaque sabe como montar “combo simples” ou “combo completo”.
Com isso:
- O código que usa o Builder não precisa saber como o objeto é montado em detalhes.
- É fácil criar diferentes versões do mesmo objeto mudando apenas o Builder concreto.
- A lógica de construção fica centralizada e organizada.
Analogia: Montagem de um Sanduíche
Seção intitulada “Analogia: Montagem de um Sanduíche”Pense em uma lanchonete universitária que faz sanduíches:
- O sanduíche pronto é o Produto.
- A pessoa que monta o sanduíche (cozinheiro) é o Builder.
- A cozinha / sistema de pedidos, que diz “faça um X-Salada” ou “faça um Vegano”, é o Director.
Passos típicos:
- Escolher o tipo de pão.
- Escolher a proteína (carne, frango, falafel, nenhum…).
- Adicionar queijo ou não.
- Adicionar vegetais (alface, tomate, cebola…).
- Adicionar molhos (maionese, mostarda, barbecue…).
Cada tipo de sanduíche segue a mesma sequência de passos, mas com ingredientes diferentes:
- X-Salada: pão normal, carne, queijo, alface, tomate, maionese.
- Vegano: pão integral, hambúrguer de grão-de-bico, alface, tomate, cebola, molho especial sem leite.
O Builder funciona assim:
- O “cozinheiro” sabe como executar cada passo (botar pão, pôr proteína, etc.).
- A “cozinha” (Director) sabe quais passos chamar para cada tipo de sanduíche.
- No final, você chama algo como
builder.getResultado()e recebe o sanduíche montado.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Builder quando:
- Um objeto tiver muitos parâmetros, especialmente opcionais ou com combinações diferentes.
- Você quiser evitar construtores gigantes e difíceis de ler.
- Você precisar construir diferentes representações do mesmo tipo de objeto.
- O processo de construção precisar ser reutilizável e centralizado.
Exemplos em Engenharia de Software:
- Montagem de requisições HTTP complexas (headers, body, autenticação, cache…).
- Criação de objetos de configuração (config do sistema, config de build, config de ambiente).
- Geração de relatórios (HTML, PDF, CSV) usando os mesmos dados, mas mudando a forma de montar.
Como funciona (estrutura geral)
Seção intitulada “Como funciona (estrutura geral)”Em pseudocódigo orientado a objetos:
// Produtoclass Sanduiche { // ...atributos: tipoPao, proteina, queijo, vegetais, molhos, etc...}
// Builderinterface SanduicheBuilder { void reset(); void adicionarPao(); void adicionarProteina(); void adicionarQueijo(); void adicionarVegetais(); void adicionarMolhos(); Sanduiche getResultado();}
// Builders concretosclass SanduicheFrangoBuilder implements SanduicheBuilder { // ...implementa os passos montando um sanduíche de frango...}
class SanduicheVeganoBuilder implements SanduicheBuilder { // ...implementa os passos montando um sanduíche vegano...}
// Directorclass Cozinha { public Sanduiche prepararSanduicheFrango(SanduicheBuilder builder) { builder.reset(); builder.adicionarPao(); builder.adicionarProteina(); builder.adicionarQueijo(); builder.adicionarVegetais(); builder.adicionarMolhos(); return builder.getResultado(); }
// ...outros métodos para diferentes "receitas"...}Exemplo prático em Java: Sanduíche
Seção intitulada “Exemplo prático em Java: Sanduíche”Produto
Seção intitulada “Produto”public class Sanduiche { private String tipoPao; private String proteina; private boolean temQueijo; private String vegetais; private String molhos;
// Construtor padrão ou pacote Sanduiche() {}
// setters usados apenas pelo Builder void setTipoPao(String tipoPao) { this.tipoPao = tipoPao; } void setProteina(String proteina) { this.proteina = proteina; } void setTemQueijo(boolean temQueijo) { this.temQueijo = temQueijo; } void setVegetais(String vegetais) { this.vegetais = vegetais; } void setMolhos(String molhos) { this.molhos = molhos; }
@Override public String toString() { return "Sanduiche{" + "tipoPao='" + tipoPao + '\'' + ", proteina='" + proteina + '\'' + ", temQueijo=" + temQueijo + ", vegetais='" + vegetais + '\'' + ", molhos='" + molhos + '\'' + '}'; }}Builder (interface)
Seção intitulada “Builder (interface)”public interface SanduicheBuilder { void reset(); void adicionarPao(); void adicionarProteina(); void adicionarQueijo(); void adicionarVegetais(); void adicionarMolhos(); Sanduiche getResultado();}Builders concretos
Seção intitulada “Builders concretos”public class SanduicheFrangoBuilder implements SanduicheBuilder { private Sanduiche sanduiche;
@Override public void reset() { sanduiche = new Sanduiche(); }
@Override public void adicionarPao() { sanduiche.setTipoPao("Pão francês"); }
@Override public void adicionarProteina() { sanduiche.setProteina("Frango grelhado"); }
@Override public void adicionarQueijo() { sanduiche.setTemQueijo(true); }
@Override public void adicionarVegetais() { sanduiche.setVegetais("Alface, tomate"); }
@Override public void adicionarMolhos() { sanduiche.setMolhos("Maionese"); }
@Override public Sanduiche getResultado() { return sanduiche; }}public class SanduicheVeganoBuilder implements SanduicheBuilder { private Sanduiche sanduiche;
@Override public void reset() { sanduiche = new Sanduiche(); }
@Override public void adicionarPao() { sanduiche.setTipoPao("Pão integral"); }
@Override public void adicionarProteina() { sanduiche.setProteina("Hambúrguer de grão-de-bico"); }
@Override public void adicionarQueijo() { sanduiche.setTemQueijo(false); // vegano, sem queijo }
@Override public void adicionarVegetais() { sanduiche.setVegetais("Alface, tomate, cebola roxa"); }
@Override public void adicionarMolhos() { sanduiche.setMolhos("Molho especial vegano"); }
@Override public Sanduiche getResultado() { return sanduiche; }}Director (Cozinha)
Seção intitulada “Director (Cozinha)”public class Cozinha { public Sanduiche prepararSanduicheCompleto(SanduicheBuilder builder) { builder.reset(); builder.adicionarPao(); builder.adicionarProteina(); builder.adicionarQueijo(); builder.adicionarVegetais(); builder.adicionarMolhos(); return builder.getResultado(); }
public Sanduiche prepararSanduicheSimples(SanduicheBuilder builder) { builder.reset(); builder.adicionarPao(); builder.adicionarProteina(); builder.adicionarMolhos(); return builder.getResultado(); }}public class AppLanchonete { public static void main(String[] args) { Cozinha cozinha = new Cozinha();
SanduicheBuilder builderFrango = new SanduicheFrangoBuilder(); SanduicheBuilder builderVegano = new SanduicheVeganoBuilder();
Sanduiche frangoCompleto = cozinha.prepararSanduicheCompleto(builderFrango); Sanduiche veganoSimples = cozinha.prepararSanduicheSimples(builderVegano);
System.out.println(frangoCompleto); System.out.println(veganoSimples); }}Perceba:
- O código cliente não sabe detalhes de como cada sanduíche é montado.
- Para adicionar um novo tipo de sanduíche, basta criar um novo
Builderconcreto.
-
Código de construção mais legível
- Evita construtores com muitos parâmetros.
- Facilita entender “passo a passo” o que está sendo configurado.
-
Suporte fácil a variações
- Builders diferentes montam versões diferentes do mesmo tipo de objeto.
- A lógica de cada variação fica isolada na sua própria classe.
-
Objetos sempre em estado consistente
- Você pode garantir que certas etapas sempre acontecem antes de expor o objeto pronto.
-
Reutilização do processo de construção
- O Director pode ter “receitas” prontas de construção (como combos de sanduíche).
Contras
Seção intitulada “Contras”-
Mais classes
- Introduz mais código estrutural (Produto, Builder, Builders concretos, Director).
- Pode parecer exagero para objetos simples.
-
Complexidade inicial maior
- Exige conhecer a estrutura do padrão.
- Nem sempre vale a pena para casos triviais.
Relações com outros padrões
Seção intitulada “Relações com outros padrões”-
Factory Method / Abstract Factory
- Fábricas focam em criar objetos de uma vez.
- Builder foca em construir passo a passo, em especial objetos complexos.
-
Prototype
- Prototype copia um objeto já existente.
- Builder monta um objeto novo desde o início, passo a passo.
-
Composite
- Builder pode ser usado para montar estruturas complexas de objetos (árvores, hierarquias), muitas vezes combinando com Composite.
-
Fluent Interface
- Builders muitas vezes expõem uma interface fluente, permitindo encadear chamadas (
.setX().setY().build()), melhorando a legibilidade.
- Builders muitas vezes expõem uma interface fluente, permitindo encadear chamadas (
Variação comum: Builder “Fluente” simplificado
Seção intitulada “Variação comum: Builder “Fluente” simplificado”Uma forma muito comum de usar Builder em Java moderno (sem Director explícito) é ter um Builder interno na própria classe:
public class Pedido { private String cliente; private String enderecoEntrega; private double desconto;
private Pedido() {}
public static class Builder { private String cliente; private String enderecoEntrega; private double desconto;
public Builder cliente(String cliente) { this.cliente = cliente; return this; }
public Builder enderecoEntrega(String enderecoEntrega) { this.enderecoEntrega = enderecoEntrega; return this; }
public Builder desconto(double desconto) { this.desconto = desconto; return this; }
public Pedido build() { Pedido p = new Pedido(); p.cliente = this.cliente; p.enderecoEntrega = this.enderecoEntrega; p.desconto = this.desconto; return p; } }}Uso:
Pedido pedido = new Pedido.Builder() .cliente("Aluno Dev") .enderecoEntrega("Campus Central") .desconto(0.1) .build();Essa abordagem é muito usada em APIs Java e frameworks modernos, e segue a mesma ideia central do padrão Builder: construção de objetos complexos de forma clara e flexível.