O padrão de design Builder tem como objetivo principal simplificar a construção de objetos complexos. Esse padrão é particularmente útil quando um objeto deve ser criado com muitos atributos opcionais ou obrigatórios, ou quando o processo de criação de um objeto é composto por múltiplas etapas e configurações. Os problemas específicos que o padrão Builder tenta resolver incluem:
-
Construção de Objetos Complexos: O Builder facilita a criação de objetos que possuem múltiplos componentes ou propriedades, especialmente quando algumas dessas propriedades são opcionais. Isso elimina a necessidade de construtores sobrecarregados ou complexos.
-
Legibilidade e Manutenção: Ao usar o padrão Builder, o código de construção torna-se mais legível e fácil de manter. Ao contrário de uma longa lista de parâmetros em um construtor, que pode ser confusa, o Builder permite a configuração passo a passo com métodos claros.
-
Imutabilidade de Objetos: O Builder pode ser usado para construir objetos imutáveis sem expor os dados do objeto durante a construção. Uma vez que um objeto é construído, ele não pode ser alterado, o que é particularmente útil em ambientes multithread.
-
Separação de Responsabilidades: Ele separa a construção de um objeto complexo de sua representação, permitindo que o mesmo processo de construção crie representações diferentes.
-
Fluência na Interface (Fluent Interface): Os Builders frequentemente utilizam o padrão de Fluent Interface, onde métodos de configuração retornam a própria instância do Builder, permitindo encadeamentos de chamadas de método. Isso torna o código cliente mais expressivo.
-
Controle sobre o Processo de Construção: O Builder fornece maior controle sobre o processo de construção do objeto, permitindo a construção de um objeto passo a passo e possibilitando a validação em cada etapa.
Em resumo, o padrão Builder é ideal para situações onde você está lidando com objetos que têm muitos parâmetros de construção, especialmente se muitos deles são opcionais ou se a construção é um processo multi-etapas. Ele oferece uma solução mais limpa e organizada do que o uso de múltiplos construtores, construtores com muitos parâmetros ou o padrão JavaBeans.
Exemplo de implementação
Definindo uma classe com uma classe Builder interna
public class Carro {
private final String marca;
private final String modelo;
private final int ano;
private final String cor;
private Carro(Builder builder) {
this.marca = builder.marca;
this.modelo = builder.modelo;
this.ano = builder.ano;
this.cor = builder.cor;
}
public static class Builder {
private String marca;
private String modelo;
private int ano;
private String cor;
public Builder marca(String marca) {
this.marca = marca;
return this;
}
public Builder modelo(String modelo) {
this.modelo = modelo;
return this;
}
public Builder ano(int ano) {
this.ano = ano;
return this;
}
public Builder cor(String cor) {
this.cor = cor;
return this;
}
public Carro build() {
return new Carro(this);
}
}
// Métodos getters aqui...
}
Por que os atributos da classe acima estão como
final?Os atributos da classe
Carroforam declarados comofinalpara ilustrar uma prática comum quando se utiliza o padrão Builder, especialmente em cenários onde imutabilidade é desejada. Aqui estão os motivos principais para essa escolha:
- Imutabilidade: Declarar atributos como
finalgarante que eles só podem ser atribuídos uma vez, ou seja, na criação do objeto. Isso leva à imutabilidade do objeto, o que é uma prática recomendada em muitos casos, pois torna o código mais seguro em ambientes multithread e evita estados inconsistentes do objeto.- Segurança de Threads: Em ambientes multithread, a imutabilidade de objetos é crucial para a segurança e integridade dos dados. Objetos imutáveis são naturalmente thread-safe, pois seu estado não pode ser modificado após a criação.
- Consistência de Dados: Uma vez que um objeto imutável é criado, seu estado não muda, o que garante a consistência de dados ao longo do tempo de vida do objeto.
- Uso com o Builder: No padrão Builder, o objetivo é construir um objeto passo a passo e, uma vez construído, o objeto não deve mudar. A utilização de atributos
finalem conjunto com o padrão Builder ajuda a reforçar essa característica.- Design Claro: A utilização de
finalem atributos também transmite uma mensagem clara sobre a intenção do design do objeto: uma vez criado, o objeto não é destinado a ter seu estado alterado. No entanto, é importante notar que a decisão de tornar os atributosfinaldepende dos requisitos específicos da aplicação e do design da classe. Em alguns casos, você pode precisar de objetos mutáveis, e nesse caso, os atributos não seriam declarados comofinal. O padrão Builder pode ser utilizado tanto com classes imutáveis quanto mutáveis, adaptando-se conforme necessário ao contexto do projeto.
Utilizando o Builder para Criar uma instância
public class Main {
public static void main(String[] args) {
Carro carro = new Carro.Builder()
.marca("Toyota")
.modelo("Corolla")
.ano(2020)
.cor("Preto")
.build();
System.out.println("Carro criado: " + carro.getModelo());
}
}