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) construtor excesso params

  • …​ poucos parâmetros: preceito do Clean Code, favorece Programação Funcional.

  • A chamada vira uma linha infinita: construtor excesso params chamada1

  • E pode ser pior ainda em questões de legibilidade: construtor excesso params chamada2

  • Poderíamos criar construtores sobrecarregados, mas a classe pode crescer muito e desviar o foco construtor sobrecarregado

  • 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

modelagem builder
Figure 1. Modelagem do Padrão Builder para uma classe fictícia "ClasseComplexa"

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

1.6. Exemplo de Uso

A figura abaixo apresenta a modelagem do padrão Builder para a classe Produto.

builder produto
Figure 2. Modelagem do padrão Builder para uma classe Produto

1.7. Detalhes de Implementação

  • Métodos na classe Builder: sem prefixo, with ou set

  • set é o padrão de IDEs como IntelliJ e NetBeans

  • Nome do método builder: create(), createNomeObjeto(), newNomeObjeto() ou build()

  • 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.

Veja um projeto de exemplo que cria uma builder para uma classe Paciente aqui (zip)

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.