Simplificando com Builder Pattern
Uma boa pratica no desenvolvimento de sistemas com Java é aproveitar os diversos padrões de projetos existentes da melhor forma possível, de modo que, facilitem tanto o processo de desenvolvimento quanto os processos de atualizações e manutenções de aplicativos. Um padrão bastante útil é o Builder. Este tem como objetivo eliminar a complexidade na criação de objetos e também deixar mais intuitivo este processo. Neste tutorial será apresentada a forma mais básica de como podemos utilizar o padrão Builder.
1. As classes do projeto sem Builder Pattern
Para que se veja de forma mais clara a vantagem do Builder Pattern, vamos ver um exemplo que envolve a seguintes classes: Pessoa
, Endereco
e Telefone
, conforme Figura 1:
Figura 1
A seguir veja o código fonte destas três classes. A classe Telefone
possui três atributos: ddd
, numero
e tipo
. O campo tipo
é um enum TipoFone
que define se o telefone cadastrado é residencial, celular ou comercial.
package com.mballem.pattern.domain; /** * Created by https://www.mballem.com */ public class Telefone { private int ddd; private int numero; private TipoFone tipo; public Telefone(int ddd, int numero, TipoFone tipo) { this.ddd = ddd; this.numero = numero; this.tipo = tipo; } public int getDdd() { return ddd; } public int getNumero() { return numero; } public TipoFone getTipo() { return tipo; } public enum TipoFone { RESIDENCIAL, CELULAR, COMERCIAL } @Override public String toString() { return "Telefone{" + "ddd=" + ddd + ", numero=" + numero + ", tipo=" + tipo + '}'; } }
Na Listagem 2 temos a classe Endereco
com atributos referentes ao cadastro de um endereço.
package com.mballem.pattern.domain; /** * Created by https://www.mballem.com */ public class Endereco { private String logradouro; private String numero; private String complemento; private String bairro; private String cidade; private String pais; //métodos setters e getters omitidos. @Override public String toString() { return "Endereco{" + "logradouro='" + logradouro + '\'' + ", numero='" + numero + '\'' + ", complemento='" + complemento + '\'' + ", bairro='" + bairro + '\'' + ", cidade='" + cidade + '\'' + ", pais='" + pais + '\'' + '}'; } }
Veja agora – Listagem 3 – a classe Pessoa
. Nesta classe temos como atributos nome
, sobrenome
, dia
, mes
e ano
. Além destes, o atributo endereco
, correspondente a classe Endereco
e uma lista de telefones do tipo Telefone
.
package com.mballem.pattern.domain; import java.util.ArrayList; import java.util.List; /** * Created by https://www.mballem.com */ public class Pessoa { private String nome; private String sobrenome; private int dia; private int mes; private int ano; private Endereco endereco; private List telefones = new ArrayList(); //métodos getters/setters omitidos. @Override public String toString() { return "Pessoa{" + "nome='" + nome + '\'' + ", sobrenome='" + sobrenome + '\'' + ", dia=" + dia + ", mes=" + mes + ", ano=" + ano + ", endereco=" + endereco + ", telefones=" + telefones + '}'; } }
2. Criando um objeto Pessoa
Na Listagem 4 vamos criar um objeto Pessoa
a partir da classe CadastroBySetMethod
. Como se pode notar, é necessário criar uma instancia para a classe Pessoa
e outra para a classe Endereco
. Uma lista precisa ser criada para a inserção dos objetos do tipo Telefone
no objeto pessoa
. Como são inseridos dois números de telefones, precisamos criar duas instancias de Telefone
e depois adicionar na lista telefones
.
package com.mballem.pattern; import com.mballem.pattern.domain.Endereco; import com.mballem.pattern.domain.Pessoa; import com.mballem.pattern.domain.Telefone; import java.util.ArrayList; import java.util.List; /** * Created by https://www.mballem.com */ public class CadastroBySetMethod { public static void main(String[] args) { Pessoa pessoa = new Pessoa(); pessoa.setNome("Alice"); pessoa.setSobrenome("dos Santos"); pessoa.setDia(22); pessoa.setMes(5); pessoa.setAno(1980); Endereco endereco = new Endereco(); endereco.setLogradouro("Rua das Oliveiras"); endereco.setNumero("272"); endereco.setComplemento("Bloco B"); endereco.setCidade("Porto Alegre"); endereco.setBairro("Centro"); endereco.setPais("Brasil"); pessoa.setEndereco(endereco); List telefones = new ArrayList(); Telefone residencial = new Telefone(51, 32221236, Telefone.TipoFone.RESIDENCIAL); Telefone celular = new Telefone(51, 99623632, Telefone.TipoFone.CELULAR); telefones.add(residencial); telefones.add(celular); pessoa.setTelefones(telefones); System.out.println(pessoa.toString()); } }
Embora o processo apresentado na Listagem 4 seja o mais natural a se fazer, podes utilizar o padrão Builder para reduzir o número de linhas exigidas e a complexidade deste código.
3. Criando a classe PessoaBuilder
Parte da complexidade do código apresentado na Listagem 4 é a necessidade de se criar uma instancia para cada objeto. Além é claro, se faz necessário criar uma lista para adicionar cada um dos telefones adicionados ao objeto pessoa
. Isto faz com que varias linhas de códigos sejam necessárias. É claro que essas linhas poderiam ser diminuídas se ao invés de se usar métodos set()
, usássemos os construtores das classes com atributos, como foi feito na classe Telefone
. Porém desta forma, teríamos o construtor da classe Pessoa
com um número excessivo de argumentos, assim como o da classe Endereco
, o que não é algo considerado bom de trabalhar.
Usando o padrão Builder podemos facilitar a criação de um objeto pessoa, deixando que as instancias das classes envolvidas, assim como, a lista de telefones sejam criadas em um ponto central, que é a classe PessoaBuilder
da Listagem 5.
package com.mballem.pattern.builder; import com.mballem.pattern.domain.Endereco; import com.mballem.pattern.domain.Pessoa; import com.mballem.pattern.domain.Telefone; import java.util.ArrayList; import java.util.List; /** * Created by Marcio Ballem on 22/09/2014. */ public class PessoaBuilder { private Pessoa pessoa; private Endereco endereco; private List telefones = new ArrayList(); /** * Cria uma instancia para o objeto Pessoa e Endereco. */ public PessoaBuilder() { this.pessoa = new Pessoa(); this.endereco = new Endereco(); } /** * Cria uma instancia para PessoaBuilder * @return uma instancia de PessoaBuilder */ public static PessoaBuilder builder() { return new PessoaBuilder(); } /** * Adiciona no objeto pessoa o valor para nome e sobrenome. * @param nome * @param sobrenome * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addNome(String nome, String sobrenome) { this.pessoa.setNome(nome); this.pessoa.setSobrenome(sobrenome); return this; } /** * Adiciona no objeto pessoa os valores para dia, mes e ano. * @param dia * @param mes * @param ano * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addDtNascimento(int dia, int mes, int ano) { this.pessoa.setDia(dia); this.pessoa.setMes(mes); this.pessoa.setAno(ano); return this; } /** * Adiciona ao objeto pessoa o valor para logradouro. * @param logradouro * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addLogradouro(String logradouro) { this.endereco.setLogradouro(logradouro); return this; } /** * Adiciona ao objeto pessoa o valor para numero. * @param numero * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addNumero(String numero) { this.endereco.setNumero(numero); return this; } /** * Adiciona ao objeto pessoa o valor para complemento. * @param complemento * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addComplemento(String complemento) { this.endereco.setComplemento(complemento); return this; } /** * Adiciona ao objeto pessoa o valor para bairro. * @param bairro * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addBairro(String bairro) { this.endereco.setBairro(bairro); return this; } /** * Adiciona ao objeto pessoa o valor para cidade. * @param cidade * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addCidade(String cidade) { this.endereco.setCidade(cidade); return this; } /** * Adiciona ao objeto pessoa o valor para país * @param pais * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addPais(String pais) { this.endereco.setPais(pais); return this; } /** * Adiciona ao objeto pessoa uma lista de telefones. * @param ddd * @param numero * @param tipoFone * @return retorna a instancia atual da classe PessoaBuilder. */ public PessoaBuilder addFone(int ddd, int numero, Telefone.TipoFone tipoFone) { this.telefones.add(new Telefone(ddd, numero, tipoFone)); return this; } /** * Para recuperar a instancia de Pessoa. * @return retorna o objeto Pessoa atual. */ public Pessoa get() { this.pessoa.setEndereco(this.endereco); this.pessoa.setTelefones(this.telefones); return this.pessoa; } }
O método construtor da classe PessoaBuilder
cria uma instancia para os objetos Pessoa
, Endereco
e também para a lista de telefones ( java.util.List
). O método estático builder()
retorna uma instancia para a classe PessoaBuilder
. Os métodos como nome iniciados por add()
, como addNome()
, recebem como argumento o valor a ser inserido no objeto e estes métodos retornam a instancia atual de PessoaBuilder
, criadas ou pelo método builder()
ou por new PessoaBuilder()
. Já o método get()
tem como objetivo retornar uma instancia da classe Pessoa
com os valores que foram inseridos ao objeto pessoa
por meio dos métodos add()
. Na Figura 2 é possível ver o diagrama de classe que representa esta nova implementação:
Figura 2
4. Usando o Builder Pattern
Veja agora, na Listagem 6, como criar um objeto Pessoa
por meio da classe PessoaBuilder
. Observe como este código se torna muito mais simples e menos complexo que o código apresentado na Listagem 4. Neste exemplo, usamos o método estático builder()
para ter acesso aos métodos add()
da classe PessoaBuilder
. Assim, podemos adicionar os valores referentes a cada atributo da classe Pessoa
, Endereco
e Telefone
sem que seja necessário criar instancias e usar os métodos set()
, já que isso já foi implementado em PessoaBuilder
. Após inserir todos os valores desejados, o método get()
encerra o processo retornando e atribuindo a instancia criada para o atributo pessoa
já com todos os valores dos campos adicionados.
package com.mballem.pattern; import com.mballem.pattern.builder.PessoaBuilder; import com.mballem.pattern.domain.Pessoa; import com.mballem.pattern.domain.Telefone; /** * Created by Marcio Ballem on 22/09/2014. */ public class Cadastro { public static void main(String[] args) { Pessoa pessoa = PessoaBuilder.builder() .addNome("Alice", "dos Santos") .addDtNascimento(22, 5, 1980) .addLogradouro("Rua das Oliveiras") .addNumero("272") .addComplemento("Bloco B") .addBairro("Centro") .addCidade("Porto Alegre") .addPais("Brasil") .addFone(51, 32221236, Telefone.TipoFone.RESIDENCIAL) .addFone(51, 99623632, Telefone.TipoFone.CELULAR) .get(); System.out.println(pessoa.toString()); } }
Você também, ao invés de usar o método builder()
, pode preferir criar uma instancia de PessoaBuilder
diretamente no código, como no exemplo da Listagem 7.
PessoaBuilder pessoaBuilder = new PessoaBuilder() .addNome("Alice", "dos Santos") .addDtNascimento(22, 5, 1980) .addLogradouro("Rua das Oliveiras") .addNumero("272") .addComplemento("Bloco B") .addBairro("Centro") .addCidade("Porto Alegre") .addPais("Brasil") .addFone(51, 32221236, Telefone.TipoFone.RESIDENCIAL) .addFone(51, 99623632, Telefone.TipoFone.CELULAR); Pessoa pessoa = pessoaBuilder.get(); System.out.println(pessoa.toString());
O padrão de projeto Builder, apresentado neste tutorial, demonstra ser uma maneira interessante e inteligente para simplificar a criação de objetos que podem se tornar complexos. Ele pode também tornar os métodos mais intuitivos para a inserção de valores, do que os métodos set()
. Por exemplo, o enquanto uma classe teria o método setTelefone()
, referente ao atributo telefone, na classe Builder você pode criar o método addTelefone()
, adicionaTelefone()
, insereTelefone()
, ou apenas telefone()
. Este padrão pode ser muito útil também quando trabalhamos com objetos de mesmo tipo, como com herança, o que será apresentando em um tutorial futuro.