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

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

Listagem 2. Classe Endereco.

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.

Listagem 3. Classe Pessoa.

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.

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

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

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.

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...