Pular para o conteúdo
Criacionais

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

HttpRequest.java
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.