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.
Listagem 1. Classe Telefone.
package com.mballem.pattern.domain;
/**
* Created by http://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.
Listagem 2. Classe Endereco.
package com.mballem.pattern.domain;
/**
* Created by http://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
.
Listagem 3. Classe Pessoa.
package com.mballem.pattern.domain;
import java.util.ArrayList;
import java.util.List;
/**
* Created by http://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
.
Listagem 4. Classe CadastroBySetMethod.
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 http://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.
Listagem 5. Classe PessoaBuilder.
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.
Listagem 6. Classe Cadastrar.
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.
Listagem 7. Usando new PessoaBuilder().
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.