Builder
Descrição
O Builder separa a construção de um objeto complexo da sua representação, permitindo que o mesmo processo de construção produza representações diferentes. Em vez de montar o objeto de uma só vez por um construtor com muitos parâmetros, a criação é dividida em etapas nomeadas e explícitas.
Um Builder define as operações para criar cada parte do produto; implementações concretas (ConcreteBuilder) sabem como montar e guardar essas partes e expõem um método para obter o resultado final. Opcionalmente, um Director encapsula a ordem dos passos para receitas de construção recorrentes, de modo que o cliente só escolhe qual builder usar.
Na prática moderna, a variante mais comum é o builder fluente (métodos encadeados que retornam o próprio builder), usado para criar objetos imutáveis com muitos atributos opcionais e validar a consistência do objeto antes de entregá-lo.
Problemas que resolve
- Construtores com longas listas de parâmetros, muitos deles opcionais, difíceis de ler e propensos a erros de ordem (o "telescoping constructor")
- Necessidade de montar um objeto passo a passo, às vezes com etapas condicionais ou repetidas
- Mesmo processo de construção precisando gerar representações diferentes (ex.: documento em HTML, Markdown ou PDF)
- Criação de objetos imutáveis cujo estado deve estar completo e válido no momento em que são entregues
Quando usar
- O objeto tem muitos atributos, principalmente opcionais ou com valores padrão
- A construção envolve várias etapas, ordem relevante ou regras de validação entre campos
- Você quer reutilizar o mesmo algoritmo de montagem para produzir produtos diferentes
- Você precisa de objetos imutáveis, mas sem construtores gigantes
- Montagem de estruturas compostas (árvores, consultas SQL, requisições HTTP, mensagens)
Quando evitar
- O objeto é simples, com poucos campos obrigatórios; um construtor ou factory resolve
- A linguagem já oferece parâmetros nomeados e valores padrão que eliminam o problema
- Os produtos não compartilham um processo de construção comum, tornando a abstração artificial
- O custo de manter um builder sincronizado com cada mudança na classe de produto não compensa
Trade-offs
| Ganho | Custo / Risco |
|---|---|
| Código cliente legível e autodocumentado (passos nomeados) | Mais classes e código a manter (um builder por produto) |
Permite produtos imutáveis e validação centralizada em build() | Duplicação de campos entre builder e produto |
| Isola o código de construção da lógica de negócio do produto | Builder pode ficar desatualizado em relação ao produto |
| Mesmo processo gera representações diferentes | Erros de campos obrigatórios esquecidos só aparecem em tempo de execução (salvo builders tipados por etapa) |
- Acoplamento: o cliente depende do builder, não da representação interna do produto.
- Testabilidade: builders facilitam criar dados de teste ("test data builders") com valores padrão sensatos.
- Complexidade: o
Directoré opcional; muitas vezes adiciona camadas sem benefício real. - Concorrência: builders são mutáveis e normalmente não devem ser compartilhados entre threads.
Diagrama
O Client cria um ConcreteBuilder e o entrega ao Director, que chama os passos de construção na ordem correta; ao final, o cliente obtém o Product montado a partir do próprio builder via getResult().
Exemplo
Uma aplicação monta requisições HTTP com URL obrigatória e vários cabeçalhos, timeout e corpo opcionais. Um builder fluente deixa a criação legível e garante que a requisição final seja imutável e válida.
public final class HttpRequest {
private final String url, method, body;
private final Map<String, String> headers;
private final int timeoutMs;
private HttpRequest(Builder b) {
url = b.url; method = b.method; body = b.body;
headers = Map.copyOf(b.headers); timeoutMs = b.timeoutMs;
}
public static Builder builder(String url) { return new Builder(url); }
public static final class Builder {
private final String url;
private String method = "GET", body;
private final Map<String, String> headers = new HashMap<>();
private int timeoutMs = 5000;
private Builder(String url) { this.url = url; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
public HttpRequest build() {
if (body != null && method.equals("GET"))
throw new IllegalStateException("GET nao aceita corpo");
return new HttpRequest(this);
}
}
}
// Uso
HttpRequest req = HttpRequest.builder("https://api.example.com/orders")
.method("POST").header("Content-Type", "application/json")
.body("{\"id\": 42}").timeout(2000).build();
Padrões relacionados
- Abstract Factory — também cria objetos complexos, mas retorna famílias de produtos de uma vez; o Builder foca em montar um produto passo a passo
- Telescoping Constructor — antipadrão que o Builder normalmente substitui
- Factory Method — alternativa mais simples quando a criação cabe em uma única chamada
- Prototype — outra forma de evitar construção complexa, clonando um objeto já configurado
- Composite — o Builder é frequentemente usado para montar árvores Composite
- Singleton — um Director ou builder compartilhado às vezes é exposto como instância única