Builder
Separa a construção de um objeto complexo da sua representação, permitindo que o mesmo processo de construção crie diferentes representações e garantindo que o produto só seja entregue em estado válido.
Intenção
Separar o processo de montagem de um objeto complexo da sua
representação final, permitindo que o mesmo processo de construção produza
representações diferentes. O Builder encapsula cada passo em métodos individuais
e centraliza toda a validação no método build(), que só entrega
o produto quando a configuração está completa e consistente.
Catalogado por Gamma, Helm, Johnson e Vlissides no livro Design Patterns: Elements of Reusable Object-Oriented Software (1994), o Builder pertence à categoria de padrões de criação. É especialmente valioso quando o objeto final possui muitos parâmetros opcionais, quando a ordem de construção importa ou quando a validação precisa ser centralizada antes da entrega do produto.
Problema
Objetos com muitos parâmetros — alguns obrigatórios, outros opcionais, com interdependências — geram dois problemas clássicos quando construídos via construtor ou setters públicos:
-
Telescoping constructor: para cobrir todas as combinações
de parâmetros opcionais, você cria múltiplas sobrecargas
(
new Pedido(items),new Pedido(items, cupom),new Pedido(items, cupom, frete)…). O código que instancia o objeto fica ilegível e propenso a erros de ordem de parâmetros. - Objeto em estado inconsistente: com setters públicos, o objeto pode ser usado antes de estar completamente configurado. Validação prematura (no setter individual) não enxerga o estado completo; validação tardia (no método de uso) está longe demais do ponto de criação.
O Builder resolve ambos: cada parâmetro vira um método de passo com nome
descritivo, e a validação completa ocorre no build().
Builder vs Abstract Factory
A distinção mais importante — e a que mais confunde:
- Abstract Factory cria famílias de objetos relacionados de forma instantânea. O foco é na consistência da família de produtos.
- Builder constrói um único objeto complexo passo a passo. O foco é no processo de montagem e na validação centralizada antes de entregar o produto.
Em resumo: Abstract Factory é sobre o quê criar (qual família); Builder é sobre como construir (qual processo, com quais restrições).
Solução
O Builder organiza o código em quatro participantes:
-
Builder (interface ou classe base): declara os métodos de
configuração de cada passo de construção. Ex.:
setUrl(),setMetodo(),addHeader(),build(). -
ConcreteBuilder: implementa os passos, acumula o estado
intermediário e fornece o
build()que valida e retorna o produto. Em TypeScript, cada passo retornathispara encadeamento fluente. - Director (opcional): encapsula sequências de construção comuns, chamando os passos do builder numa ordem predefinida. Útil quando as mesmas configurações canônicas aparecem em múltiplos lugares.
-
Product: o objeto complexo resultante. Frequentemente
imutável — após
build(), nenhum método de configuração pode mais alterá-lo.
O segredo do padrão está no build(): é ali que a validação
centralizada ocorre. Antes de build(), o builder é mutável e
tolerante; após, o produto é imutável e garantidamente válido.
Estrutura
Diagrama UML simplificado com o exemplo de builder de requisição HTTP:
HttpRequest (Product — imutável)
┌──────────────────────────────────────────────────────┐
│ + readonly url: string │
│ + readonly metodo: "GET"|"POST"|"PUT"|"PATCH"|"DEL." │
│ + readonly headers: Record<string, string> │
│ + readonly body?: string │
└──────────────────────────────────────────────────────┘
▲
│ cria
HttpRequestBuilder (ConcreteBuilder)
┌──────────────────────────────────────────────────────┐
│ - _url: string │
│ - _metodo: Metodo │
│ - _headers: Record<string, string> │
│ - _body?: string │
├──────────────────────────────────────────────────────┤
│ + setUrl(url): this ← fluent (retorna this) │
│ + setMetodo(m): this │
│ + addHeader(k, v): this │
│ + setBody(b): this │
│ + build(): HttpRequest ← valida e entrega │
└──────────────────────────────────────────────────────┘
Fluxo de construção:
const req = new HttpRequestBuilder()
.setUrl("https://api.exemplo.com/usuarios")
.setMetodo("POST")
.addHeader("Content-Type", "application/json")
.setBody('{"nome":"Ana"}')
.build(); // ← validação centralizada ocorre aqui
Exemplos de código
Exemplo 1 — Builder de requisição HTTP com validação no build()
O builder acumula as configurações passo a passo. O método
build() valida o estado completo e cria o produto imutável —
impedindo que requisições inválidas existam no sistema.
type Metodo = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
// ── Product (imutável) ────────────────────────────────────────
class HttpRequest {
constructor(
public readonly url: string,
public readonly metodo: Metodo,
public readonly headers: Readonly<Record<string, string>>,
public readonly body?: string,
) {}
}
// ── Builder ───────────────────────────────────────────────────
class HttpRequestBuilder {
private _url: string = "";
private _metodo: Metodo = "GET";
private _headers: Record<string, string> = {};
private _body?: string;
setUrl(url: string): this {
this._url = url;
return this;
}
setMetodo(metodo: Metodo): this {
this._metodo = metodo;
return this;
}
addHeader(chave: string, valor: string): this {
this._headers[chave] = valor;
return this;
}
setBody(body: string): this {
this._body = body;
return this;
}
// Validação centralizada: ocorre uma única vez, antes de criar o produto.
build(): HttpRequest {
if (!this._url) {
throw new Error("HttpRequest: url é obrigatória.");
}
if (["POST", "PUT", "PATCH"].includes(this._metodo) && !this._body) {
throw new Error(`HttpRequest: body é obrigatório para ${this._metodo}.`);
}
return new HttpRequest(
this._url,
this._metodo,
{ ...this._headers },
this._body,
);
}
}
// ── Uso ──────────────────────────────────────────────────────
const req = new HttpRequestBuilder()
.setUrl("https://api.exemplo.com/usuarios")
.setMetodo("POST")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token123")
.setBody('{"nome":"Ana","email":"ana@exemplo.com"}')
.build();
console.log(req.metodo); // "POST"
console.log(req.url); // "https://api.exemplo.com/usuarios"
// Tentativa de build inválido:
try {
new HttpRequestBuilder()
.setUrl("https://api.exemplo.com/usuarios")
.setMetodo("POST")
.build(); // Falta body!
} catch (e) {
console.error((e as Error).message);
// → HttpRequest: body é obrigatório para POST.
}
<?php
// ── Product (imutável via readonly) ───────────────────────────
final class HttpRequest
{
/** @param array<string, string> $headers */
public function __construct(
public readonly string $url,
public readonly string $metodo,
public readonly array $headers,
public readonly ?string $body = null,
) {}
}
// ── Builder ───────────────────────────────────────────────────
class HttpRequestBuilder
{
private string $url = '';
private string $metodo = 'GET';
/** @var array<string, string> */
private array $headers = [];
private ?string $body = null;
public function setUrl(string $url): static
{
$this->url = $url;
return $this;
}
public function setMetodo(string $metodo): static
{
$this->metodo = $metodo;
return $this;
}
public function addHeader(string $chave, string $valor): static
{
$this->headers[$chave] = $valor;
return $this;
}
public function setBody(string $body): static
{
$this->body = $body;
return $this;
}
// Validação centralizada: ocorre uma única vez, antes de criar o produto.
public function build(): HttpRequest
{
if ($this->url === '') {
throw new \InvalidArgumentException('HttpRequest: url é obrigatória.');
}
if (in_array($this->metodo, ['POST', 'PUT', 'PATCH'], true) && $this->body === null) {
throw new \InvalidArgumentException(
"HttpRequest: body é obrigatório para {$this->metodo}."
);
}
return new HttpRequest($this->url, $this->metodo, $this->headers, $this->body);
}
}
// ── Uso ──────────────────────────────────────────────────────
$req = (new HttpRequestBuilder())
->setUrl('https://api.exemplo.com/usuarios')
->setMetodo('POST')
->addHeader('Content-Type', 'application/json')
->addHeader('Authorization', 'Bearer token123')
->setBody('{"nome":"Ana","email":"ana@exemplo.com"}')
->build();
echo $req->metodo . PHP_EOL; // POST
echo $req->url . PHP_EOL; // https://api.exemplo.com/usuarios
// Tentativa de build inválido:
try {
(new HttpRequestBuilder())
->setUrl('https://api.exemplo.com/usuarios')
->setMetodo('POST')
->build(); // Falta body!
} catch (\InvalidArgumentException $e) {
echo $e->getMessage() . PHP_EOL;
// → HttpRequest: body é obrigatório para POST.
}
Exemplo 2 — Director: sequências de construção reutilizáveis
O Director encapsula as sequências canônicas de construção, liberando o código cliente de repetir os mesmos passos em múltiplos lugares. O Director não sabe qual builder concreto está usando — trabalha pela interface, não pela implementação.
// Director: conhece as sequências canônicas de construção.
class ApiClientDirector {
constructor(private readonly builder: HttpRequestBuilder) {}
// Constrói uma requisição GET autenticada padrão.
buildGetAutenticado(url: string, token: string): HttpRequest {
return this.builder
.setUrl(url)
.setMetodo("GET")
.addHeader("Authorization", `Bearer ${token}`)
.addHeader("Accept", "application/json")
.build();
}
// Constrói uma requisição POST com JSON autenticada padrão.
buildPostJson(url: string, token: string, payload: unknown): HttpRequest {
return this.builder
.setUrl(url)
.setMetodo("POST")
.addHeader("Authorization", `Bearer ${token}`)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.setBody(JSON.stringify(payload))
.build();
}
}
// ── Uso ──────────────────────────────────────────────────────
const director = new ApiClientDirector(new HttpRequestBuilder());
const getReq = director.buildGetAutenticado(
"https://api.exemplo.com/perfil",
"abc123",
);
console.log(`${getReq.metodo} ${getReq.url}`);
// → GET https://api.exemplo.com/perfil
const postReq = director.buildPostJson(
"https://api.exemplo.com/pedidos",
"abc123",
{ produto: "Caderno", quantidade: 2 },
);
console.log(`${postReq.metodo} ${postReq.url}`);
// → POST https://api.exemplo.com/pedidos
<?php
// Director: conhece as sequências canônicas de construção.
class ApiClientDirector
{
public function __construct(
private readonly HttpRequestBuilder $builder,
) {}
// Constrói uma requisição GET autenticada padrão.
public function buildGetAutenticado(string $url, string $token): HttpRequest
{
return $this->builder
->setUrl($url)
->setMetodo('GET')
->addHeader('Authorization', "Bearer {$token}")
->addHeader('Accept', 'application/json')
->build();
}
// Constrói uma requisição POST com JSON autenticada padrão.
public function buildPostJson(string $url, string $token, array $payload): HttpRequest
{
return $this->builder
->setUrl($url)
->setMetodo('POST')
->addHeader('Authorization', "Bearer {$token}")
->addHeader('Content-Type', 'application/json')
->addHeader('Accept', 'application/json')
->setBody(json_encode($payload))
->build();
}
}
// ── Uso ──────────────────────────────────────────────────────
$director = new ApiClientDirector(new HttpRequestBuilder());
$getReq = $director->buildGetAutenticado(
'https://api.exemplo.com/perfil',
'abc123',
);
echo "{$getReq->metodo} {$getReq->url}" . PHP_EOL;
// → GET https://api.exemplo.com/perfil
$postReq = $director->buildPostJson(
'https://api.exemplo.com/pedidos',
'abc123',
['produto' => 'Caderno', 'quantidade' => 2],
);
echo "{$postReq->metodo} {$postReq->url}" . PHP_EOL;
// → POST https://api.exemplo.com/pedidos
Quando usar
- Objetos com muitos parâmetros opcionais: quando um construtor começa a ter 4 ou mais parâmetros — especialmente com vários opcionais — o Builder torna o código cliente legível e resistente a erros de ordem.
-
Quando a validação centralizada importa: o
build()é o único ponto onde todas as regras são verificadas em conjunto, garantindo que o produto só existe em estado válido. - Quando o produto deve ser imutável: o Builder coleta a configuração mutável e entrega um produto imutável — padrão clássico para objetos de valor (value objects) e DTOs.
- Quando a ordem de construção importa: o Director encapsula a ordem, garantindo que passos obrigatórios nunca sejam esquecidos.
Quando evitar
- Objetos simples com poucos parâmetros fixos: 2–3 parâmetros obrigatórios sem opcionais não justificam uma classe builder intermediária.
-
Quando não há validação real no build(): se o
build()apenas instancia sem validar, o Builder é só açúcar sintático para setters encadeados — avalie se o ganho de legibilidade justifica a classe extra. - Quando a imutabilidade do produto não importa: se o objeto precisa ser mutável após a criação de qualquer forma, o padrão perde parte do seu apelo principal.
Prós e contras
Prós
- Elimina o telescoping constructor: cada parâmetro opcional tem seu próprio método com nome descritivo.
- Validação centralizada no
build(): o produto só existe se for válido — impossível ter objeto em estado inconsistente. - Suporta produtos imutáveis: o builder é mutável durante a configuração; o produto entregue é imutável.
- Legível no site de uso:
.setUrl(...).setMetodo("POST").addHeader(...).build()documenta a intenção melhor que uma lista posicional de parâmetros. - O Director permite reutilizar sequências de construção comuns sem duplicação.
Contras
- Mais código: cada produto exige uma classe builder equivalente — overhead para objetos simples.
- O cliente pode chamar
build()esquecendo passos obrigatórios — a validação resolve em runtime, mas não em tempo de compilação. - O encadeamento fluente com
thisem TypeScript pode causar surpresas com subclasses; o tipo de retorno exige cuidado.
Armadilhas comuns
1. Builder sem validação no build() — apenas fluent setters
Atenção: O erro mais comum é criar métodos encadeados que
apenas copiam valores para campos, e um build() que instancia
sem validar nada. Isso é uma API fluente sobre setters — não é o padrão Builder.
A essência do padrão é a validação centralizada e o produto
garantidamente válido. Sem isso, você tem complexidade extra sem
o benefício principal.
2. Reutilizar o builder após build()
Chamar métodos de configuração após build() e chamar
build() novamente pode produzir produtos com estado diferente
do esperado, especialmente quando o builder acumula itens (como headers).
Duas estratégias comuns: (a) lançar exceção se build() for
chamado duas vezes, ou (b) resetar o estado interno após cada
build(), tornando o builder reutilizável de forma explícita.
3. Confundir Builder com Abstract Factory
Abstract Factory entrega objetos prontos de uma família em uma única chamada. Builder monta um único objeto complexo passo a passo com validação. Se você precisa garantir que botão e checkbox sejam do mesmo tema, use Abstract Factory. Se você precisa configurar uma requisição HTTP com muitos cabeçalhos opcionais e validar tudo antes de enviá-la, use Builder.
4. Director desnecessário
O Director é opcional. Se não houver sequências de construção reutilizadas em múltiplos lugares, o cliente pode chamar os passos do builder diretamente sem a camada extra. Introduza o Director apenas quando a mesma sequência aparece em três ou mais pontos do código — DRY aplicado à construção.
Padrões relacionados
O Builder interage com outros padrões de criação e estruturais:
O Abstract Factory é frequentemente comparado ao Builder: ambos são padrões de criação, mas Abstract Factory cria famílias de objetos simples de uma vez, enquanto Builder monta um único objeto complexo passo a passo com validação centralizada. O Prototype pode ser usado dentro de um builder quando a criação parte de uma configuração base clonada — o builder clona o protótipo e aplica customizações por cima. O Composite frequentemente é o produto sendo construído pelo Builder: estruturas em árvore (documentos, pipelines, árvores de UI) são candidatas naturais à construção passo a passo.