Builder é um design pattern criacional que foca no desacoplamento entre a complexa lógica de construção e a representação de um objeto.
As vezes a construção de um objeto pode ser complexa, devido a validações de parâmetros, busca de informações em arquivos ou bases de dados, ou até mesmo pela quantidade de parâmetros que um construtor espera receber. Nestes casos, se você “misturar” a lógica de criação do objeto com a lógica que define seu comportamento, você poderá acabar por ter uma classe pouco coesa e difícil de reaproveitar.
O design pattern Builder vem para resolver este problema, colocando tanto a lógica criacional quanto a lógica comportamental eu seus devidos lugares, por proporcionar o encapsulamento da construção de um objeto, de um modo tão claro que você poderá até desenvolver diferentes implementações para a criação de um mesmo objeto ou de um mesmo builder.
Iremos criar uma classe Customer para representar um cliente, adicionar algumas validações de parâmetros e fazer com que um deles seja obrigatório para demonstrar o pattern.
Abaixo está o diagrama UML com sua representação:
Algumas considerações importantes a respeito do diagrama:
Abaixo estão as classes Java que implementam o pattern. Alguns javadocs, comentários, getters, setters, construtores e sobrescritas de toString
foram omitidos para fins de legibilidade. Por favor acesse o repositório no GitHub para obter a versão completa deste código.
Customer: a classe que precisamos instanciar para utilizarmos em nossas classes clientes:
package com.bgasparotto.designpatterns.builder; import java.util.HashSet; import java.util.Set; public class Customer { private long id; private String name; private String city; private Set<Phone> phones; public Customer(long id, String name, String city, Set<Phone> phones) { this.id = id; this.name = name; this.city = city; this.phones = phones; } public Customer(String name, String city, Set<Phone> phones) { this(0, name, city, phones); } public Customer(String name) { this(0, name, null, new HashSet<Phone>()); } // Getters and setters. }
Phone: somente uma dependência de Customer, algo do tipo com o qual nos deparamos em nosso dia a dia:
package com.bgasparotto.designpatterns.builder; public class Phone { private long id; private String number; public Phone(long id, String number) { this.id = id; this.number = number; } public Phone(String number) { this(0, number); } // Getters and setters. }
CustomerBuilder e CustomerBuilderImpl: o contrato e a implementação do pattern, respectivamente:
package com.bgasparotto.designpatterns.builder; public interface CustomerBuilder { CustomerBuilder fromCity(String cityName); CustomerBuilder hasPhone(String phoneNumber); Customer build(); }
package com.bgasparotto.designpatterns.builder; import java.util.Set; public class CustomerBuilderImpl implements CustomerBuilder { private Customer customer; public CustomerBuilderImpl(String name) { this.customer = new Customer(name); } @Override public CustomerBuilder fromCity(String cityName) { customer.setCity(cityName); return this; } @Override public CustomerBuilder hasPhone(String phoneNumber) { Phone phone = new Phone(phoneNumber); Set<Phone> phones = customer.getPhones(); phones.add(phone); return this; } @Override public Customer build() { return customer; } }
Vamos dizer que precisamos instanciar um objeto de Customer e tudo que temos em mão é o valor para seu atributo name, que por sinal, é o único parâmetro obrigatório. Neste caso, independente de usarmos ou não o pattern o código fica limpo, porém, um pouco menor se não aplicarmos o pattern:
// Instancia o Customer passando o nome diretamente customer = new Customer("Bruno"); // Instancia o Customer passando o nome para o Builder CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.build();
Agora vamos dizer que além do nome, precisaremos também passar o nome da cidade cityName. As duas soluções continuam limpas e simples, porém, sem o pattern, estamos acoplando nosso código aos detalhes de implementação do Customer, manualmente invocando seu método setCity(String).
// Instancia o Customer e define sua cidade. customer = new Customer("Bruno"); customer.setCity("Sao Paulo"); // Instancia o Customer passando a cidade para a interface fluente CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.fromCity("Sao Paulo").build();
No próximo passo, além de passar os argumentos anteriores, precisamos passar também um número de telefone para criar nossa instância, possibilitando já observar que a complexidade do código aumenta conforme a lista de argumentos também cresce se não utilizarmos o pattern:
// Passando a cidade e o telefone sem o pattern. customer = new Customer("Bruno"); customer.setCity("Sao Paulo"); Phone mobile = new Phone("999999999"); Set<Phone> phones = customer.getPhones(); phones.add(mobile); // Passando a cidade e o telefone com o pattern. CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.fromCity("Sao Paulo").hasPhone("999999999").build();
Além das linhas de código adicionais quando precisamos passar mais argumentos, é importante notar que na versão do nosso código sem o pattern, foi preciso acoplar nosso código cliente aos setters da classe Customer para configurar seus atributos. Isto pode se tornar um problema no futuro caso precisemos por exemplo, remover os setters da classe Customer e tratar sua configuração somente no construtor para torná-la imutável, ou até mesmo modificar seu nível hierárquico em relação a classe Phone. Neste ponto, sem o pattern teríamos que modificar todas as classes do sistema que de algum modo criam uma instância de customer, enquanto que com o pattern, focaríamos nossa manutenção apenas nas classes do Builder.
This guide will show you how to create a Python function decorator with a few…
This guide will show you how to fix the error Got permission denied while trying…
This guide will show you how to create a Python virtual environment on Intellij IDEA…
This tutorial will quickly show you how to to find and kill processes on Linux,…
This guide shows a possible solution for Python error Relocation R_X86_64_PC32 against symbol can not…
I condensed below a cheat sheet of Kubernetes useful commands. I will keep updating this…