Gosto muito de padrões que além de melhorar a manutenção do sistema, aumentam a legibilidade do código. Um desses padrões é o Builder, que mostra uma forma elegante de tratar classes com grande número de propriedades se tornando complexos para serem construídos.
“Antes de construir um muro pergunto sempre quem estou murando e quem estou deixando de fora.”
– Robert Frost
Builder
A definição do padrão Builder segundo o GOF é “…separar a construção de um objeto complexo de sua representação de modo que o mesmo processo de construção possa criar diferentes representações…”, isso significa que o padão visa simplificar a construção de objetos sem que precisemos conhecer os detalhes dessa construção.
Aplicabilidade
Utilize quando você precisa separar a criação de um objeto complexo das partes que o constituem e como elas se combinam. Outro caso é quando o processo de construção precisa permitir diferentes formas de representação do objeto construído.
Implementação
A classe Soldado representa a motivação para utilizar o padrão, já que possui uma quantidade de propriedades que torna a criação dos construtores bem trabalhosa:
public class Soldado {
private final Aparelhos aparelho;
private final String nome;
private final Especialidade especialidade;
private final ArmaPrimaria armaPrimaria;
private final ArmaSecundaria armaSecundaria;
private final Colete colete;
private Soldado(Builder builder) {
this.aparelho = builder.aparelho;
this.nome = builder.nome;
this.especialidade = builder.especialidade;
this.armaPrimaria = builder.armaPrimaria;
this.armaSecundaria = builder.armaSecundaria;
this.colete = builder.colete;
}
.
.
.
Repare que a classe só possui um construtor privado, que apenas receberá o Builder para atribuição de valores do objeto,
e não vários tentando prever N possibilidades de construção. A classe Builder será uma inner class estática da
classe Soldado
, para acessar o construtor privado e passar os valores:
public static class Builder {
private final String nome;
private final Especialidade especialidade;
private Aparelhos aparelho;
private ArmaPrimaria armaPrimaria;
private ArmaSecundaria armaSecundaria;
private Colete colete;
public Builder(Especialidade especialidade, String nome) {
if (especialidade == null || nome == null) {
throw new IllegalArgumentException("Especialidade ou nome não podem ser vazios");
}
this.especialidade = especialidade;
this.nome = nome;
}
public Builder comArmaPrimaria(ArmaPrimaria armaPrimaria) {
this.armaPrimaria = armaPrimaria;
return this;
}
public Builder comArmaSecundaria(ArmaSecundaria armaSecundaria) {
this.armaSecundaria = armaSecundaria;
return this;
}
public Builder comColete(Colete colete) {
this.colete = colete;
return this;
}
public Builder comAparelho(Aparelhos aparelho) {
this.aparelho = aparelho;
return this;
}
public Soldado build() {
return new Soldado(this);
}
}
O builder criado recebe propriedades obrigatórias através do construtor, as outras propriedades são recebidas por
métodos que são nomeados para melhorar o entendimento da utilização do builder e sempre retornam um objeto com o estado
atual das propriedades. Já o método build()
retorna uma instância preenchida com os valores definidos ao longo do
código. Na
classe Aplicacao
temos o exemplo de utilização do padrão:
private static final Logger LOGGER=LoggerFactory.getLogger(Aplicacao.class);
public static void main(String[]args){
Soldado assalto=new Soldado.Builder(Especialidade.ASSALTO,"Cpt Nascimento")
.comArmaPrimaria(ArmaPrimaria.FUZIL)
.comArmaSecundaria(ArmaSecundaria.PISTOLA)
.comColete(Colete.CERAMICO)
.build();
LOGGER.info(assalto.toString());
Soldado suporte=new Soldado.Builder(Especialidade.SUPORTE,"Rambo")
.comArmaPrimaria(ArmaPrimaria.METRALHADORA)
.comAparelho(Aparelhos.C4)
.build();
LOGGER.info(suporte.toString());
Soldado engenheiro=new Soldado.Builder(Especialidade.ENGENHEIRO,"Jack Bauer")
.comAparelho(Aparelhos.SMOKE)
.comArmaSecundaria(ArmaSecundaria.PISTOLA)
.comColete(Colete.KEVLAR)
.build();
LOGGER.info(engenheiro.toString());
Soldado batedor=new Soldado.Builder(Especialidade.BATEDOR,"Chris Kyle")
.comArmaPrimaria(ArmaPrimaria.RIFLE)
.comArmaSecundaria(ArmaSecundaria.REVOLVER)
.comColete(Colete.ALUMINIO)
.comAparelho(Aparelhos.SMOKE)
.build();
LOGGER.info(batedor.toString());
}
Repare que a sintaxe do builder deixa o código muito claro e intuitivo, além de permitir uma flexibilização das propriedades que desejamos inicializar.
Vantagens e desvantagens
Utilizar o Builder só tem sentido quando há uma abundância de parâmetros para a construção do objeto, ou seja, não se deve utilizá-lo quando há poucos parâmetros. Além disso, existe um custo de desempenho (normalmente não perceptível) já que sempre deve-se chamar o Builder antes de utilizar o objeto, em sistemas de desempenho crítica pode ser uma desvantagem.
Finalizando
O padrão Builder é uma técnica que deve ser parte do arsenal de utilitários do desenvolvedor, melhorando o código de criação de objetos, mas deve ser utilizado com cuidado e critério.
Um forte abraço e até a próxima.
Código no Github
https://github.com/ivanqueiroz/padroes-projeto-java