Padrão Criacional • GoF

Builder
Pattern

Construa objetos complexos passo a passo, separando a construção da representação final.

por Davi Moreira de Santana

scroll

O que é um
Padrão de Projeto?

Padrões de projeto são soluções típicas para problemas recorrentes no design de software orientado a objetos. Funcionam como plantas arquitetônicas que podem ser adaptadas para resolver problemas específicos no seu código.

Diferente de um algoritmo (que define passos claros para atingir uma meta), um padrão é uma descrição de alto nível de uma solução. Pense assim: um algoritmo é uma receita de comida, com etapas claras. Um padrão é uma planta de obra, onde você vê o resultado final e suas funcionalidades, mas a ordem exata de implementação depende de você.

"O padrão não é um pedaço de código específico, mas um conceito geral para resolver um problema em particular."

Refactoring.Guru
🎯

Propósito

Descreve brevemente o problema e a solução que o padrão endereça.

🔧

Motivação

Explica a fundo o problema e como a solução proposta pelo padrão é viável.

📐

Estrutura

Mostra as classes e objetos envolvidos e como eles se relacionam entre si.

Por que aprender
Padrões de Projeto?

Padrões de projeto são um toolkit de soluções testadas e aprovadas para problemas comuns. Mesmo que você nunca encontre exatamente esses problemas, conhecê-los ensina a resolver todo tipo de desafio usando princípios de design orientado a objetos.

Além disso, padrões definem uma linguagem comum que facilita a comunicação entre desenvolvedores. Ao dizer "use um Builder aqui", toda a equipe entende imediatamente a estrutura proposta, sem precisar de longas explicações.

🧩

Reutilização

Soluções prontas que economizam tempo e reduzem bugs conhecidos no design do software.

💬

Comunicação

Vocabulário compartilhado entre devs. "Builder", "Singleton", "Observer" carregam significado imediato.

🏗️

Arquitetura

Ensinam princípios de design que melhoram toda a qualidade do código produzido.

Classificação
dos Padrões

Os 23 padrões clássicos do GoF (Gang of Four) são divididos em três categorias com base no seu propósito: Criacionais, Estruturais e Comportamentais.

Criacionais

Mecanismos de criação de objetos que aumentam flexibilidade e reutilização.

Factory Method Abstract Factory Builder ★ Prototype Singleton

Estruturais

Como montar objetos e classes em estruturas maiores mantendo flexibilidade.

Adapter Bridge Composite Decorator Facade Flyweight Proxy

Comportamentais

Algoritmos e designação de responsabilidades entre objetos.

Chain of Resp. Command Iterator Mediator Observer State Strategy Visitor

O Propósito

B

Construir objetos complexos passo a passo

O Builder permite produzir diferentes tipos e representações de um objeto usando o mesmo código de construção, sem precisar de um construtor com dezenas de parâmetros.

"O Builder é um padrão de projeto criacional que permite a você construir objetos complexos passo a passo. O padrão permite que você produza diferentes tipos e representações de um objeto usando o mesmo código de construção."

Refactoring.Guru — Builder Pattern

O Problema

Imagine um objeto complexo que necessita de inicialização passo a passo de muitos campos e objetos agrupados. Esse tipo de código de inicialização normalmente fica enterrado dentro de um construtor monstruoso com muitos parâmetros. Ou pior: espalhado por todo o código cliente.

A Analogia da Casa

Para construir uma casa simples, você precisa de paredes, piso, porta, janelas e telhado. Mas e se quiser uma casa maior, com jardim, sistema de aquecimento, encanamento e fiação elétrica?


A abordagem mais simples seria estender a classe base e criar subclasses para cada configuração possível. Mas isso rapidamente leva a uma explosão de subclasses.


Outra opção seria um construtor gigantesco com todos os parâmetros possíveis na classe base. Elimina subclasses, mas cria chamadas monstruosas onde a maioria dos parâmetros fica sem uso.

Observe a construção passo a passo...

❌ O Problema — Construtor Telescópico
// Construtor com parâmetros demais
// A maioria dos parâmetros fica sem uso na maior parte do tempo
class Casa {
constructor(
paredes: number,
portas: number,
janelas: number,
temGaragem: boolean,
temPiscina: boolean,
temJardim: boolean,
temEstátuas: boolean,
temSistemaAquecimento: boolean,
// ... e mais 15 parâmetros
) { }
}

// Chamada horrível — o que significa cada boolean?
const casa = new Casa(
4,
1,
4,
true,
false,
true,
false,
true
);

A Solução

O padrão Builder sugere extrair o código de construção do objeto para fora da própria classe e movê-lo para objetos separados chamados builders. O padrão organiza a construção em uma série de etapas (construirParedes(), construirPorta(), etc.). Para criar um objeto, você executa apenas as etapas necessárias.

A parte crucial: você não precisa chamar todas as etapas. Pode invocar apenas aquelas que são necessárias para produzir uma configuração específica.

Etapa 1

Interface Builder

Define todas as etapas de construção possíveis, comuns a todos os tipos de builders.

Etapa 2

Concrete Builders

Implementam as etapas de construção de formas diferentes. Cada builder pode produzir um produto completamente diferente.

Etapa 3

Produtos

São os objetos resultantes. Produtos de builders diferentes não precisam compartilhar a mesma interface.

Etapa 4

Director (Opcional)

Define a ordem de execução das etapas de construção. Encapsula receitas comuns de construção para reutilização.

Estrutura do Padrão Builder

«interface» Builder
+ reset()
+ setParedes(...)
+ setPortas(...)
+ setJanelas(...)
+ setTelhado(...)
CasaBuilder
- casa: Casa
+ reset()
+ setParedes(...)
+ setPortas(...)
+ getResultado(): Casa
Casa (Produto)
- paredes: number
- portas: number
- janelas: number
- temPiscina: bool
- temGaragem: bool
Director
- builder: Builder
+ construirCasaSimples()
+ construirMansao()
usa o builder para
orquestrar a construção
✅ A Solução — Builder Pattern em TypeScript
// Interface Builder // Interface Builder
interface CasaBuilder {
reset(): void;
setParedes(n: number): CasaBuilder;
setPortas(n: number): CasaBuilder;
setJanelas(n: number): CasaBuilder;
setPiscina(tem: boolean): CasaBuilder;
setGaragem(tem: boolean): CasaBuilder;
}
// Concrete Builder
class CasaConcretaBuilder implements CasaBuilder {
private casa: Casa;

constructor() { this.reset(); }
reset() { this.casa = new Casa(); }

setParedes(n) { this.casa.paredes = n; return this; }
setPortas(n) { this.casa.portas = n; return this; }
setJanelas(n) { this.casa.janelas = n; return this; }
setPiscina(t) { this.casa.piscina = t; return this; }
setGaragem(t) { this.casa.garagem = t; return this; }

getResultado(): Casa {
const result = this.casa;
this.reset();
return result;
}
}
// Director — orquestra receitas de construção
class Arquiteto {
construirCasaSimples(builder: CasaBuilder) {
builder.reset();
builder.setParedes(4).setPortas(1).setJanelas(4);
}

construirMansao(builder: CasaBuilder) {
builder.reset();
builder.setParedes(12).setPortas(6).setJanelas(20)
.setPiscina(true).setGaragem(true);
}
}
// Uso
const builder = new CasaConcretaBuilder();
const arquiteto = new Arquiteto();

arquiteto.construirMansao(builder);
const mansao = builder.getResultado();

// Ou sem director — construção customizada
builder.setParedes(6).setJanelas(8).setPiscina(true);
const casaCustom = builder.getResultado();

Builder Interativo

Clique nas etapas para construir seu objeto passo a passo. Veja o output em tempo real.

ETAPAS DE CONSTRUÇÃO

// Clique nas etapas acima para construir... const builder = new CasaBuilder(); builder.reset();

Quando Aplicar
o Builder?

🔩 Objetos com muitas configurações opcionais

Quando o construtor da classe tem 5, 10, 15 parâmetros opcionais, o Builder elimina o "construtor telescópico" e torna a criação legível.

🌲 Construção de árvores Composite ou estruturas complexas

O Builder permite construir produtos passo a passo e defer a execução de algumas etapas sem quebrar o resultado final. Isso é útil para montar árvores de objetos recursivamente.

🔄 Produtos com diferentes representações

Quando o mesmo processo de construção deve produzir representações diferentes do produto (ex: um carro real vs. um manual do carro), builders concretos diferentes resolvem isso.

📦 Isolamento do produto em construção

O Builder mantém o produto privado durante a construção. O código cliente nunca recebe um objeto incompleto ou em estado instável.

Exemplos do Mundo Real

Contexto Builder Produto
Query SQL QueryBuilder String SQL (SELECT...WHERE...)
HTTP Request RequestBuilder Objeto Request configurado
Docker Compose ServiceBuilder docker-compose.yaml
K8s Manifests DeploymentBuilder Deployment + Service + Ingress
StringBuilder append(), insert() String final otimizada

Exemplo Prático
em Go

No exemplo abaixo temos dois builders concretos: NormalBuilder e IglooBuilder. Ambos implementam a mesma interface IBuilder, mas constroem casas completamente diferentes.

builder.go — Interface e Director
package main

type IBuilder interface {
setDoorType()
setWindowType()
setNumFloor()
getHouse() House
}

type Director struct {
builder IBuilder
}

func (d *Director) setBuilder(b IBuilder) {
d.builder = b
}

func (d *Director) buildHouse() House {
d.builder.setDoorType()
d.builder.setWindowType()
d.builder.setNumFloor()
return d.builder.getHouse()
}

// main.go
func main() {
normalBuilder := getBuilder("normal")
iglooBuilder := getBuilder("igloo")

director := &Director{}
director.setBuilder(normalBuilder)
normalHouse := director.buildHouse()

director.setBuilder(iglooBuilder)
iglooHouse := director.buildHouse()
}

Exemplos Práticos

Um HTTP Request Builder simplifica a construção de requisições HTTP complexas. Em vez de passar múltiplos parâmetros para uma função, você encadeia métodos para configurar cada aspecto da requisição de forma legível.

🔗 HTTP Request Builder — Node.js
// HTTP Request Builder com method chaining
class HTTPRequestBuilder {
constructor(url) {
this.url = url;
this.method = 'GET';
this.headers = {};
this.params = {};
this.timeout = 5000;
}

setMethod(method) {
this.method = method;
return this;
}

setHeader(key, value) {
this.headers[key] = value;
return this;
}

setParam(key, value) {
this.params[key] = value;
return this;
}

setTimeout(ms) {
this.timeout = ms;
return this;
}

build() {
return {
url: this.url,
method: this.method,
headers: this.headers,
params: this.params,
timeout: this.timeout
};
}
}

// Uso
const request = new HTTPRequestBuilder('https://api.example.com/users')
.setMethod('POST')
.setHeader('Content-Type', 'application/json')
.setHeader('Authorization', 'Bearer token123')
.setParam('name', 'John')
.setParam('email', 'john@example.com')
.setTimeout(10000)
.build();

Um SQL Query Builder permite construir queries complexas de forma programática e intuitiva. Em vez de concatenar strings SQL manualmente, você usa métodos para adicionar cláusulas WHERE, JOINs, ORDER BY e LIMIT, mantendo o código limpo e seguro contra SQL injection.

🗄️ SQL Query Builder — Python
# SQL ORM Query Builder com method chaining
class QueryBuilder:
__init__(self, table):
self.table = table
self.select_cols = ['*']
self.where_conditions = []
self.joins = []
self.order_by_col = None
self.limit_val = None

select(self, *cols):
self.select_cols = list(cols) if cols else ['*']
return self

where(self, condition):
self.where_conditions.append(condition)
return self

join(self, table, on_condition):
self.joins.append({'table': table, 'on': on_condition})
return self

order_by(self, column, direction='ASC'):
self.order_by_col = {'col': column, 'dir': direction}
return self

limit(self, n):
self.limit_val = n
return self

build(self):
query = f'SELECT {", ".join(self.select_cols)} FROM {self.table}'

for join in self.joins:
query += f' JOIN {join["table"]} ON {join["on"]}'

if self.where_conditions:
query += ' WHERE ' + ' AND '.join(self.where_conditions)

if self.order_by_col:
query += f' ORDER BY {self.order_by_col["col"]} {self.order_by_col["dir"]}'

if self.limit_val:
query += f' LIMIT {self.limit_val}'

return query

}

# Uso
query = QueryBuilder('users')
.select('id', 'name', 'email')
.join('orders', 'users.id = orders.user_id')
.where('age > 18')
.where('status = "active"')
.order_by('created_at', 'DESC')
.limit(10)
.build()

Prós e Contras

✅ Prós

Construção passo a passo, com possibilidade de adiar etapas ou executá-las recursivamente.
Reutilização do mesmo código de construção para diferentes representações do produto.
Princípio de Responsabilidade Única: isola código de construção complexo da lógica de negócios.
O produto fica privado durante a construção, evitando que o cliente receba objetos incompletos.
Suporte a method chaining, tornando o código cliente fluente e legível.

❌ Contras

Complexidade geral do código aumenta, já que são necessárias novas classes para cada builder e seus produtos.
Para objetos simples com poucos campos, o overhead do Builder pode não compensar.
Em linguagens dinâmicas como Python/JS, named arguments ou object literals muitas vezes resolvem o problema sem precisar do padrão completo.
Pode criar acoplamento entre o builder e os detalhes internos do produto.

Parâmetros
Obrigatórios

Às vezes um objeto não pode existir sem uma determinada propriedade, como um name ou um id. Existem duas formas de garantir isso no Builder.

Opção 1: Construtor do Builder

O parâmetro obrigatório vai direto no constructor do Builder. Sem ele, você nem consegue instanciar o Builder, o erro aparece na hora certa, antes de qualquer método ser chamado.

Ideal quando o parâmetro é estrutural, o objeto simplesmente não faz sentido sem ele.

Opção 2: Validação no build()

O parâmetro pode ser definido em qualquer momento via método encadeado, mas o build() verifica se ele foi informado antes de retornar o objeto. Se não foi, lança um erro.

Ideal quando você quer manter a flexibilidade de ordem nas chamadas, mas ainda impor a regra no final.

Opção 1 — Parâmetro no construtor
class BurgerBuilder {
// name é obrigatório: sem ele, o Builder nem é criado
constructor(name) {
this.name = name
this.bacon = false
this.salad = false
}

addBacon() { this.bacon = true; return this }
addSalad() { this.salad = true; return this }

build() {
return new Burger(this)
}
}

// Uso correto
const burger = new BurgerBuilder("Cheddar Burger")
.addBacon()
.addSalad()
.build()

// Erro imediato: não é possível instanciar sem o name
const invalid = new BurgerBuilder() // ← erro aqui
Opção 2 — Validação no build()
class BurgerBuilder {
constructor() {
this.name = null
this.bacon = false
this.salad = false
}

setName(name) { this.name = name; return this }
addBacon() { this.bacon = true; return this }
addSalad() { this.salad = true; return this }

build() {
// validação centralizada antes de retornar
if (!this.name) throw new Error("name é obrigatório")
return new Burger(this)
}
}

// A ordem das chamadas é livre
const burger = new BurgerBuilder()
.addBacon()
.setName("Cheddar Burger") // pode vir em qualquer posição
.addSalad()
.build()

// Erro só aparece no final
const invalid = new BurgerBuilder().addBacon().build() // ← erro aqui

Relação com
Outros Padrões

Builder + Bridge

Bridge é um padrão que separa "o que um objeto faz" de "como ele faz". Imagine um controle remoto (abstração) que pode funcionar com qualquer TV (implementação), você troca a TV sem mudar o controle.

No Builder, essa divisão aparece naturalmente: o Director é a abstração (sabe o que construir e em qual ordem), enquanto cada Builder concreto é a implementação (sabe como montar cada parte). Você pode trocar o Builder sem alterar o Director.

Builder + Composite

Composite é um padrão para estruturas em árvore, onde um objeto pode conter outros objetos do mesmo tipo, como uma pasta que contém arquivos e outras pastas.

Construir essa hierarquia manualmente é complicado. O Builder encaixa perfeitamente aqui: você usa seus métodos passo a passo para adicionar nós, filhos e subárvores, deixando o processo organizado e legível, inclusive com chamadas recursivas para ramificações profundas.

Builder vs. Abstract Factory

Abstract Factory também cria objetos, mas o foco é diferente: ele produz famílias inteiras de objetos relacionados de uma vez, como uma fábrica que entrega cadeira, mesa e sofá todos no mesmo estilo.

O Builder, por outro lado, monta um único objeto complexo passo a passo. A diferença prática: se você precisa de controle sobre cada etapa da construção (e quer o objeto só no final), use Builder. Se precisa de vários objetos compatíveis entre si de uma vez, use Abstract Factory.

Builder como Singleton

Singleton é um padrão que garante que uma classe tenha apenas uma instância em toda a aplicação, como uma conexão única com o banco de dados que todos os módulos compartilham.

Em alguns cenários, faz sentido que o próprio Builder (ou a Abstract Factory / Prototype) seja um Singleton: assim, toda a aplicação reutiliza o mesmo builder configurado, evitando criar múltiplas instâncias desnecessárias de um objeto que é caro de inicializar.

O Builder em
uma frase

"Separe a construção de um objeto complexo da sua representação para que o mesmo processo de construção possa criar diferentes representações."

Gang of Four — Design Patterns (1994)
🏗️

Tipo

Criacional

4

Participantes

Builder, ConcreteBuilder, Product, Director

Representações

Um processo, infinitos produtos diferentes