1. Builder (Construtor)
1.1. Definição
📘"Separa a construção de um objeto complexo da sua representação, de modo que o mesmo processo de construção possa criar diferentes representações." (GoF)
Em outras palavras, separa o código utilizado para a instanciação de objetos complexos do código da classe de tal objeto. As representações são as instâncias da classe.
Outra definição diz que "O padrão Builder encapsula a construção de um objeto, permitindo que ele seja construído em passos." (UCPP)
1.2. Distinção do Termo
O padrão Builder significa construtor, mas não confunda com um método construtor existente em classes. Apesar dos dois terem a mesma finalidade – construir, instanciar um objeto – são mecanismos diferentes utilizados para isso. Um Builder exige o uso de um método construtor. Ele não consegue instanciar um objeto por si só.
1.3. Aplicabilidade
O padrão pode ser usado pra instanciar um objeto complexo quando:
-
A instanciação exige muitos parâmetros no construtor (normalmente bem mais que 3)
-
… poucos parâmetros: preceito do Clean Code, favorece Programação Funcional.
-
A chamada vira uma linha infinita:
-
E pode ser pior ainda em questões de legibilidade:
-
Poderíamos criar construtores sobrecarregados, mas a classe pode crescer muito e desviar o foco
-
Há dependência entre atributos: se for definido valor pra "x", precisa definir pra "y".
-
Existem atributos obrigatórios e outros opcionais.
-
Regras de validação envolvem atributos em conjunto (pra validar "x" é preciso verificar "y" também), etc.
Normalmente o primeiro item já pode ser o suficiente para aplicarmos o padrão Builder a uma classe.
1.4. Modelagem
No diagrama, a classe Builder foi representada como uma Inner Class: uma classe declarada dentro de outra. Se o código da ClasseComplexa
for extenso, incluir a Builder como uma Inner Class vai tornar o arquivo mais extenso ainda. Nestes casos, o ideal é criar a Builder em um arquivo separado.
1.5. Princípios utilizados
-
Separation of Concerns (SoC) - Separação de Preocupações: separa as regras de negócio da classe do processo de instanciação da mesma.
-
Alta Coesão
1.6. Exemplo de Uso
A figura abaixo apresenta a modelagem do padrão Builder para a classe Produto
.
1.7. Detalhes de Implementação
-
Métodos na classe Builder: sem prefixo,
with
ouset
-
set é o padrão de IDEs como IntelliJ e NetBeans
-
Nome do método builder:
create()
,createNomeObjeto()
,newNomeObjeto()
oubuild()
-
Construtor package ou private
1.8. Problemas / Desvantagens
-
*Duplicação absurda de código
-
*Viola o princípio Don’t Repeat Yourself (DRY)
-
*Novos atributos? Precisa atualizar a Builder!
-
Exige a criação de um objeto Builder para poder instanciar os objetos desejados.
-
Dificulta a injeção de dependências pela falta de um construtor sem parâmetros na classe cujos objetos serão instanciados.
*Isto é um problema se a Builder for escrita manualmente. |
1.9. Soluções para Alguns dos Problemas
Implementar o padrão Builder é algo cansativo. A quantidade de código duplicado é absurda, pois todos os atributos existentes na classe de modelo terá que existir na classe Builder, assim como todos os setters. Ao adicionar novos atributos, a classe Builder tem que ser atualizada. Pra piorar, temos que repetir todo este processo para cada classe que desejarmos aplicar o padrão.
Quando você implementa o padrão como mostrado, você inclusive estará violando o princípio Don’t Repeat Yourself (DRY): Não Repita Você mesmo. Estamos repetindo a declaração dos atributos e setters ao criar uma Builder.
Para evitar tais inconvenientes, podemos utilizar IDEs como o IntelliJ e NetBeans para gerar tais Builders automaticamente. Este vídeo mostra como isso pode ser feito. Neste caso, ao incluir um novo atributo, o mais fácil é excluir a builder e usar o IDE pra criá-la novamente.
Alternativamente, podemos usar uma biblioteca que gera uma Builder quando percisarmos, se encarregando de atualizar automaticamente a Builder sempre que atualizarmos a classe de negócio que ele constrói. Uma biblioteca excelente para isto é a Immutables, que dentre vários recursos, gera Builders por meio de uma simples anotação.
Basta adicionar tal biblioteca no arquivo pom.xml
e inclui a anotação @Builder.Constructor
no construtor da classe desejada, como Produto
e pronto: "automagicamente" a classe ProdutoBuilder
é criada/atualizada quando compilamos o projeto.
2. Padrões Relacionados
Padrões que possuem similaridades ou podem ser usados em conjunto:
3. Exercícios
Implemente uma classe Produto
com os atributos abaixo. Utilize um construtor contendo apenas os parâmetros obrigatórios, marcados com asterisco:
-
id: long
-
*titulo: String
-
descricao: String
-
marca: String
-
modelo: String
-
estoque: int (valor padrão zero)
-
*preco: double (deve ser maior que zero)
-
*dataCadastro: LocalDate (não pode ser menor que a data atual)
-
*dataUltimaAtualizacao: LocalDate (não pode ser menor que a data atual)
-
urlFoto: String
-
*categoria: String
-
peso: double
-
altura: double
-
largura: double
-
profundidade: double
O modelo só pode ser atribuído se a marca também for. Há como resolver isso da forma como a implementação foi sugerida acima?
Resolva o problema aplicando o padrão Builder, realizando as alterações necessárias na classe Produto
.
Em uma aplicação mais realista, existiriam classes específicas como Marca , Modelo , Categoria e outras. Um Modelo estaria vinculado a uma Marca . Assim, na classe Produto teríamos apenas um atributo Modelo . Se este for setado, ele deveria estar vinculado a uma Marca . Mas este é apenas um exemplo didático simples, focando apenas na aplicação do Builder.
|