Classes com muitos atributos opcionais em seus métodos construtores podem trazer bastante sujeira para o código, além de aumentar a probabilidade de erros quanto mais atributos forem necessários para instanciar um objeto da classe em questão.
Considere o exemplo a seguir da classe Produto, que possui 8 atributos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
import java.math.BigDecimal; public class Produto { private Long id; private String nome; private String descricao; private BigDecimal valor; private String cor; private String altura; private String largura; private String comprimento; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getDescricao() { return descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public BigDecimal getValor() { return valor; } public void setValor(BigDecimal valor) { this.valor = valor; } public String getCor() { return cor; } public void setCor(String cor) { this.cor = cor; } public String getAltura() { return altura; } public void setAltura(String altura) { this.altura = altura; } public String getLargura() { return largura; } public void setLargura(String largura) { this.largura = largura; } public String getComprimento() { return comprimento; } public void setComprimento(String comprimento) { this.comprimento = comprimento; } } |
Digamos que um produto possa ser criado com apenas dois atributos, id e nome e, além disso, qualquer outra combinação possível de atributos. Nesse caso, precisamos de um construtor para atender esse requisito O código abaixo mostra a classe Produto com seu construtor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import java.math.BigDecimal; public class Produto { private Long id; private String nome; private String descricao; private BigDecimal valor; private String cor; private String altura; private String largura; private String comprimento; public Produto(Long id, String nome, String descricao, BigDecimal valor, String cor, String altura, String largura, String comprimento) { super(); this.id = id; this.nome = nome; this.descricao = descricao; this.valor = valor; this.cor = cor; this.altura = altura; this.largura = largura; this.comprimento = comprimento; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getDescricao() { return descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public BigDecimal getValor() { return valor; } public void setValor(BigDecimal valor) { this.valor = valor; } public String getCor() { return cor; } public void setCor(String cor) { this.cor = cor; } public String getAltura() { return altura; } public void setAltura(String altura) { this.altura = altura; } public String getLargura() { return largura; } public void setLargura(String largura) { this.largura = largura; } public String getComprimento() { return comprimento; } public void setComprimento(String comprimento) { this.comprimento = comprimento; } } |
O problema dessa abordagem é a verbosidade no código para criar produtos. Veja, por exemplo, como a criação de alguns produtos com diferentes atributos pode poluir facilmente o código. Além disso, essa abordagem é muito propícia a erros. Na hora de criar um produto você deverá redobrar a atenção, muitas vezes olhando a classe produto várias vezes, para não trocar a posição de atributos do mesmo tipo.
1 2 |
Produto produtoBasico = new Produto(1L, "Camiseta Básica", null, null, null, null, null, null); Produto produtoBasicoComCor = new Produto(1L, "Camiseta Básica", null, null, "Azul", null, null, null); |
É preciso passar null para os atributos que não desejamos, tornando o código menos legível, mais trabalhoso e muito propenso a erros. Uma possível solução é fazer uma sobrecarga de métodos construtores, que recebem apenas os atributos que queremos. Nessa abordagem, a classe produto ficaria assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
import java.math.BigDecimal; public class Produto { private Long id; private String nome; private String descricao; private BigDecimal valor; private String cor; private String altura; private String largura; private String comprimento; public Produto(Long id, String nome) { super(); this.id = id; this.nome = nome; } public Produto(Long id, String nome, String cor) { super(); this.id = id; this.nome = nome; this.cor = cor; } public Produto(Long id, String nome, String descricao, BigDecimal valor, String cor, String altura, String largura, String comprimento) { super(); this.id = id; this.nome = nome; this.descricao = descricao; this.valor = valor; this.cor = cor; this.altura = altura; this.largura = largura; this.comprimento = comprimento; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getDescricao() { return descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public BigDecimal getValor() { return valor; } public void setValor(BigDecimal valor) { this.valor = valor; } public String getCor() { return cor; } public void setCor(String cor) { this.cor = cor; } public String getAltura() { return altura; } public void setAltura(String altura) { this.altura = altura; } public String getLargura() { return largura; } public void setLargura(String largura) { this.largura = largura; } public String getComprimento() { return comprimento; } public void setComprimento(String comprimento) { this.comprimento = comprimento; } } |
E a criação dos produtos ficaria assim:
1 2 |
Produto produtoBasico = new Produto(1L, "Camiseta Básica"); Produto produtoBasicoComCor = new Produto(1L, "Camiseta Básica", "Azul"); |
Essa abordagem melhora bastante a facilidade de criar os objetos, mas ainda há um problema. E se quisermos também uma forma de criar produtos apenas com o id, o nome e a descrição. Parece que é só criar um construtor que receba um long e duas strings e o problema está resolvido, mas como já existe um método construtor com essa assinatura (o construtor que cria um produto básico com cor), não é possível adicionar um novo método construtor com essa especificação.
Isso é um problema. Mas é um problema já conhecido e resolvido por aí. Por isso a importância dos padrões de projeto. Para resolver isso, vamos utilizar o padrão Effective Java’s Builder, que nos ajuda a criar de forma segura e elegante classes com muitos atributos opcionais.
O padrão Effective Java’s Builder tem como base o conceito de interface fluente, onde os métodos podem ser encadeados e chamados como se estivesse escrevendo um texto. No caso do builder, a ideia é construir um objeto básico com os atributos obrigatórios e depois ir chamando outros métodos para os atributos opcionais de forma encadeada. Veja como fica a classe Produto com o padrão builder implementado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
import java.math.BigDecimal; public class Produto { private Long id; private String nome; private String descricao; private BigDecimal valor; private String cor; private String altura; private String largura; private String comprimento; private Produto(Builder builder) { super(); this.id = builder.id; this.nome = builder.nome; this.descricao = builder.descricao; this.valor = builder.valor; this.cor = builder.cor; this.altura = builder.altura; this.largura = builder.largura; this.comprimento = builder.comprimento; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getDescricao() { return descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public BigDecimal getValor() { return valor; } public void setValor(BigDecimal valor) { this.valor = valor; } public String getCor() { return cor; } public void setCor(String cor) { this.cor = cor; } public String getAltura() { return altura; } public void setAltura(String altura) { this.altura = altura; } public String getLargura() { return largura; } public void setLargura(String largura) { this.largura = largura; } public String getComprimento() { return comprimento; } public void setComprimento(String comprimento) { this.comprimento = comprimento; } public static class Builder { private Long id; private String nome; private String descricao; private BigDecimal valor; private String cor; private String altura; private String largura; private String comprimento; public Builder (Long id, String nome) { this.id = id; this.nome = nome; } public Builder descricao(String descricao) { this.descricao = descricao; return this; } public Builder valor(BigDecimal valor) { this.valor = valor; return this; } public Builder cor(String cor) { this.cor = cor; return this; } public Builder altura(String altura) { this.altura = altura; return this; } public Builder largura(String largura) { this.largura = largura; return this; } public Builder comprimento(String comprimento) { this.comprimento = comprimento; return this; } public Produto build() { return new Produto(this); } } } |
A principal mudança foi a criação de uma classe interna chamada Builder para construir instâncias da classe Produto. O construtor da classe produto foi deixado com privado, dessa forma só é possível construir uma instância de produto por meio da classe interna Builder.
A classe Builder possui todos os atributos de produto e um construtor para um produto básico que recebe o id e o nome. Além disso, cada método para os demais atributos (descricao, valor, cor, etc) retorna o próprio builder, tornando possível as chamadas encadeadas.
Para construir uma instância da classe Produto é necessário chamar o método build(), que chama o construtor privado da classe Produto passando o builder com os atributos de produto. O construtor da classe Produto apenas obtém os dados do builder.
Veja agora como fica a criação de um produto básico e de um produto com outros atributos.
1 2 3 4 5 6 7 8 9 10 |
Produto produtoBasico = new Produto.Builder(1L, "Camiseta Básica").build(); Produto produtoBasicoComCor = new Produto.Builder(1L, "Camiseta Básica") .cor("Azul") .build(); Produto produtoBasicoComCorDescricao = new Produto.Builder(1L, "Camiseta Básica") .cor("Azul") .descricao("Camiseta de Algodão") .build(); |
A construção do objeto ficou muito mais simples, com uma escrita natural, além de tornar o código menos poluído e menos propenso a erros na construção dos objetos.
Além disso, o código fica mais flexível. É possível adicionar novas funcionalidades no builder, por exemplo um método validate() que, antes de construir o objeto, valida os dados e pode lançar uma exceção do tipo IllegalStateExeception caso haja alguma inconsistência nos dados.