Builder
Separa a construção de um objeto complexo da sua representação, para que o mesmo processo de construção possa criar representações diferentes.
Intenção
Fornecer um processo de construção passo a passo para um objeto complexo, permitindo que o mesmo procedimento de montagem produza diferentes tipos e representações, mantendo o código de construção isolado da estrutura interna do objeto.
Problema
Você precisa criar um objeto que exige muitos passos de configuração, parâmetros opcionais, ou sub-objetos aninhados. Um construtor com dezenas de parâmetros fica ilegível (o problema do 'construtor telescópico'), e nem toda combinação de parâmetros é válida. Você também quer reaproveitar a mesma lógica de construção para produzir representações diferentes do objeto.
Solução
Extraia o código de construção do objeto para uma classe Builder separada. Organize a construção em uma série de passos bem nomeados (setWhere, addJoin, limit, etc.). O cliente chama apenas os passos que precisa. Uma classe Director opcional pode encapsular sequências de construção comuns para reaproveitamento.
Participantes
- Builder — declara a interface passo a passo para construir partes do produto
- ConcreteBuilder — implementa a interface do builder e monta o produto, mantendo o controle do estado atual
- Director (opcional) — define a ordem em que os passos de construção são chamados para montar configurações comuns
- Product — o objeto complexo sendo construído, cuja representação interna pode variar
Vantagens
- Constrói objetos passo a passo, adiando ou omitindo passos opcionais conforme necessário
- Reaproveita o mesmo código de construção para montar diferentes representações do produto
- Isola a lógica de construção complexa da lógica de negócio (Princípio da Responsabilidade Única)
- Elimina construtores telescópicos e torna o código de criação de objetos autoexplicativo
Desvantagens
- Aumenta a complexidade geral do código ao introduzir múltiplas classes builder novas
- Exige que o cliente conheça os diferentes tipos de builder quando não há um Director
- O produto só fica disponível após build() ser chamado, então objetos parciais podem ser usados por engano se a API não for cuidadosamente projetada
Analogia do mundo real
Construir uma casa segue um processo geral fixo — lançar a fundação, erguer as paredes, instalar o telhado, adicionar encanamento e elétrica — mas os detalhes de cada passo variam muito. Uma cabana de madeira e um castelo de pedra passam pelas mesmas etapas, mas o construtor (a equipe de obra) faz escolhas diferentes em cada passo. Você, como diretor, especifica o que quer; o construtor decide como executar.
Casos de uso
- Query builders SQL ou NoSQL que montam consultas programaticamente
- Builders de requisições HTTP com headers, corpo, parâmetros de query e timeouts opcionais
- Objetos de configuração complexos com muitos campos opcionais (ex: configuração de servidor)
- Geradores de documentos ou relatórios que produzem múltiplos formatos de saída a partir dos mesmos dados
- Fábricas de dados de teste que constroem objetos de domínio com valores padrão sensatos
Exemplos de código
import java.util.LinkedHashMap;
import java.util.Map;
public final class HttpRequest {
private final String method;
private final String url;
private final Map<String, String> headers;
private final String body;
private final int timeout;
private HttpRequest(Builder b) {
this.method = b.method;
this.url = b.url;
this.headers = Map.copyOf(b.headers);
this.body = b.body;
this.timeout = b.timeout;
}
public String method() { return method; }
public String url() { return url; }
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private String method = "GET";
private String url = "/";
private final Map<String, String> headers = new LinkedHashMap<>();
private String body;
private int timeout = 30_000;
public Builder method(String method) { this.method = method; return this; }
public Builder url(String url) { this.url = url; return this; }
public Builder header(String key, String value) { headers.put(key, value); return this; }
public Builder body(String body) { this.body = body; return this; }
public Builder timeout(int ms) { this.timeout = ms; return this; }
public HttpRequest build() {
return new HttpRequest(this);
}
}
public static void main(String[] args) {
HttpRequest request = HttpRequest.builder()
.method("POST")
.url("https://api.example.com/users")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body("{"name":"Alice"}")
.timeout(5_000)
.build();
System.out.println(request.method() + " " + request.url());
// POST https://api.example.com/users
}
}Builder fluente de requisições HTTP usando uma classe Builder estática aninhada. Cada setter retorna this para encadeamento, e build() produz um HttpRequest imutável com Map.copyOf() protegendo os headers.