Builder é um dos padrões de projeto da classe dos criacionais da bíblia da Gangue dos Quadro. Esse padrão permite construir objetos complexos passo a passo e um efeito colateral disso é uma construção fluente para os objetos.
var pessoa = Pessoa.builder().nome("João").sobrenome("das Couve").idade(36).build();
Dessa forma, o que observamos do uso de builder e sua popularização está muito mais ligado à substituição de um construtor com muitos parâmetros por um design mais fluente do que propriamente o a intenção da GoF:
"...separação da construção de um objeto complexo da sua representação, de forma que o mesmo processo de construção possa criar diferentes representações."
Para diferenciar essa inteção do builder padrão, é comum chama-lo de Fluent Interface Builder.
O pattern está disponível nas IDEs para ser auto criado e ficou ainda mais conhecido no mundo Java através do Projeto Lombok que tirou um grande boilerplate e transformou em uma simples anotação.
Esse builder costuma ter o seguinte padrão:
package builder;
public class Pessoa {
private final String nome;
private final String sobrenome;
private final int idade;
public Pessoa(String nome, String sobrenome, int idade) {
this.nome = nome;
this.sobrenome = sobrenome;
this.idade = idade;
}
public static PessoaBuilder builder() { return new PessoaBuilder(); }
// Getters and setters
public static class PessoaBuilder {
private String nome;
private String sobrenome;
private int idade;
public PessoaBuilder nome(String nome) {
this.nome = nome;
return this;
}
public PessoaBuilder sobrenome(String sobrenome) {
this.sobrenome = sobrenome;
return this;
}
public PessoaBuilder idade(int idade) {
this.idade = idade;
return this;
}
public Pessoa build() { return new Pessoa(nome, sobrenome, idade); }
}
}
Mas sempre me incomodou que o builder, possuindo muitos passos, poderia contribuir para que o desenvolvedor desatento chame o build() prematuramente, isto é, com o objeto ainda incompleto. Isso só seria percebido em tempo de execução com uma exceção, ou pior, com a tentativa frustrada de acesso àquela propriedade esquecida.
Quando temos um construtor podemos impor que certos campos sejam final e isso obriga o desenvolvedor a preencher aqueles parâmetros, mas com o builder padrão, isso não é possível.
Esse incômodo, felizmente, acabou quando achei o artigo brilhante Simple Implementation of Fluent Builder - Safe Alternative To Traditional Builder do Sergiy Yevtushenko.
Esse gênio resolveu o problema e ainda de uma forma extremamente elegante!
package builder;
public class Pessoa {
private final String nome;
private final String sobrenome;
private final int idade;
public Pessoa(String nome, String sobrenome, int idade) {
this.nome = nome;
this.sobrenome = sobrenome;
this.idade = idade;
}
public static PessoaBuilder.Stage0 builder() {
return nome -> sobrenome -> idade -> () -> new Pessoa(nome, sobrenome, idade);
}
private interface PessoaBuilder {
interface Stage0 { Stage1 nome(String nome); }
interface Stage1 { Stage2 sobrenome(String sobrenome); }
interface Stage2 { Stage3 idade(int idade); }
interface Stage3 { Pessoa build(); }
}
}
Aqui, definimos, de forma obrigatória, os parâmetros e a ordem em que eles devem ser preenchidos. Nosso builder é construído com passos representados por interfaces funcionais. Por conveniência, inseri esses passos dentro de uma interface que se comporta aqui como um namespace.
- O método builder retorna o primeiro estágio, uma classe que tem apena o método nome;
- Este retorna uma classe que tem também um único método que, sobrenome;
- Sobrenome retorna a Stage3 com o método build(),
- O build cria um objeto Pessoa.
Conclusão
O builder padrão é excelente quando se deseja dar ao desenvolvedor a flexibilidade de preencher os parâmetros que achar necessário, mantendo valores padrões para aqueles que não preencher. Para uma situação em que certos parâmetros não são opcionais, a solução apresentada é uma alternativa bastante enxuta e elegante.
Top comments (0)