Higor Lima
Higor Lima12/08/2023 18:02
Compartilhe

Padrão de projeto Builder na pŕatica com Java & Typescript

    O padrão Builder é, sem dúvida, uma das melhores soluções para construir objetos complexos, ou seja, com muitos atributos. O Builder é um dos Padrões de Projeto (também chamado de Design Patterns) da programação, que nada mais são do que soluções para problemas um pouco mais genéricos. Além disso, o Builder também faz parte da família de "padrões de projetos criacionais", que, como o nome sugere, fornecem meios para a criação de um objeto, sendo isso definido pela famosa "Gang Of Four".

    A "Gang Of Four", mais conhecida como "GoF" (Gangue dos Quatros) se refere a um grupo de quatro grande autores na área de engenharia de software que escreveram o livro "Design Patterns: Elements of Reusable Object-Oriented Software". Publicado em 1994, o livro introduziu o famoso conceito de padrão de projeto.

    O problema:

    Imagine uma classe com bastante atributos, como por exemplo, Pessoa com DEZ atributos:

    image

    Para a instanciar uma classe dessas, o código ficaria menos legível e mais "feio".

    Typescript:

    class Pessoa {
      nome?: string;
      idade?: number;
      cidade?: string;
      bairro?: string;
      telefone?: string;
      email?: string;
      peso?: number;
      altura?: number;
      casado?: boolean;
      profissao?: string;
    
    
      constructor(nome?: string, idade?: number, cidade?: string, bairro?: string, telefone?:             string,email?: string, peso?: number, altura?: number, casado?: boolean, 
    profissao?: string) {
          this.nome = nome;
          this.idade = idade;
          this.cidade = cidade;
          this.bairro = bairro;
          this.telefone = telefone;
          this.email = email;
          this.peso = peso;
          this.altura = altura;
          this.casado = casado;
          this.profissao = profissao;
      }
    
    
    }
    
    
    const pessoa: Pessoa = new Pessoa("Pedro", 21, "Icó", "Centro", "4002-8922", 	 
                                    "pedro@gmail.com", 85.5, 1.87, true, "Pedreiro");
    

    Java:

    public class Pessoa {
    
    private String nome;
    private Integer idade;
    private String cidade;
    private String bairro;
    private String telefone;
    private String email;
    private BigDecimal peso;
    private BigDecimal altura;
    private boolean casado;
    private String profissao;
    
    // ------------------------------------------------------------------------------
    
    
    
    public Pessoa(String nome, Integer idade, String cidade, String bairro,
                  String telefone, String email, BigDecimal peso, BigDecimal altura,          
                  boolean casado, String profissao) {
      this.nome = nome;
      this.idade = idade;
      this.cidade = cidade;
      this.bairro = bairro;
      this.telefone = telefone;
      this.email = email;
      this.peso = peso;
      this.altura = altura;
      this.casado = casado;
      this.profissao = profissao;
    }
     
    public String getNome() {
      return nome;
    }
    
    public void setNome(String nome) {
      this.nome = nome;
    }
    
    public Integer getIdade() {
      return idade;
    }
    
    public void setIdade(Integer idade) {
      this.idade = idade;
    }
    
    public String getCidade() {
      return cidade;
    }
    
    public void setCidade(String cidade) {
      this.cidade = cidade;
    }
    
    public String getBairro() {
      return bairro;
    }
    
    public void setBairro(String bairro) {
      this.bairro = bairro;
    }
    
    public String getTelefone() {
      return telefone;
    }
    
    public void setTelefone(String telefone) {
      this.telefone = telefone;
    }
    
    public String getEmail() {
      return email;
    }
    
    public void setEmail(String email) {
      this.email = email;
    }
    
    public BigDecimal getPeso() {
      return peso;
    }
    
    public void setPeso(BigDecimal peso) {
      this.peso = peso;
    }
    
    public BigDecimal getAltura() {
      return altura;
    }
    
    public void setAltura(BigDecimal altura) {
      this.altura = altura;
    }
    
    public boolean isCasado() {
      return casado;
    }
    
    public void setCasado(boolean casado) {
      this.casado = casado;
    }
    
    public String getProfissao() {
      return profissao;
    }
    
    public void setProfissao(String profissao) {
      this.profissao = profissao;
    }
    
    }
    
    public class Main {
    
    public static void main(String[] args) {
    
      Pessoa pessoa = new Pessoa("Pedro", 21, "Icó", "Centro",
          "4002-8922", "pedro@gmail.com", BigDecimal.valueOf(85.5),
          BigDecimal.valueOf(1.87), true,"Pedreiro");
    }
    }
    

    Além do código ficar menos legível, fica também muito complicado lembrar quais os valores que estão sendo passados, sem a necessidade de ficar toda hora olhando o construtor ou apertar algum comando de "auto-complete" para mostrar os parâmetros que o construtor recebe (como o CTRL Espaço no IntelliJ, por exemplo). E no caso do Java, em necessidade de alguns atributos não serem obrigatórios, seria preciso mais construtores, poluindo ainda mais o código.

    Solução: Padrão Builder

    A idéia deste padrão de projeto é colocar a lógica da instanciação do objeto dentro de um outro objeto e fazê-la passo-a-passo.

    O padrão Builder foi descrito no livro "Design Patterns: Elements of Reusable Object-Oriented Software" com quatro elementos. No entanto, para facilitar a compreensão, optei por abordar apenas três deles neste artigo (caso queira saber como é feito na forma original, clique aqui):

    Builder: Interface com métodos para configurar os atributos. Sua utilização é semelhante a um método setter, com a diferença de que, em vez de não ter retorno (void), retorna uma instância da classe que implementará a interface. Além disso, apresenta o método "build" para criar o objeto complexo (neste caso, o objeto "Pessoa").

    image

    Concrete Builder: Basicamente, é a classe que implementa a interface Builder. Deve conter também todos os atributos do objeto complexo que é responsável por criar.

    Objeto Complexo: Refere-se ao objeto com múltiplos atributos, como o objeto "Pessoa". Também deve possuir um método estático que retorna uma instância da classe que implementa a interface Builder.

    image

    Implementação do padrão Builder com Java e Typescript:

    Interface de builder:

    Typescript:

    interface PessoaBuilder {
    addNome(nome: string): PessoaBuilder;
    addIdade(idade: number): PessoaBuilder;
    addCidade(cidade: string): PessoaBuilder;
    addBairro(bairro: string): PessoaBuilder;
    addTelefone(telefone: string): PessoaBuilder;
    addEmail(email: string): PessoaBuilder;
    addPeso(peso: number): PessoaBuilder;
    addAltura(altura: number): PessoaBuilder;
    addCasado(casado: boolean): PessoaBuilder;
    addProfissao(profissao: string): PessoaBuilder;
    build(): Pessoa;
    }
    

    Java:

    public interface PessoaBuilder {
    
    PessoaBuilder nome(String nome);
    PessoaBuilder idade(Integer idade);
    PessoaBuilder cidade(String cidade);
    PessoaBuilder bairro(String bairro);
    PessoaBuilder telefone(String telefone);
    PessoaBuilder email(String email);
    PessoaBuilder peso(BigDecimal peso);
    PessoaBuilder altura(BigDecimal altura);
    PessoaBuilder casado(boolean casado);
    PessoaBuilder profissao(String profissao);
    Pessoa build();
    
    }
    

    Concret builder (a classe que implementa a interface de builder):

    Typescript:

    class PessoaBuilderImpl implements PessoaBuilder {
    
    private nome: string;
    private idade: number;
    private cidade: string;
    private bairro: string;
    private telefone: string;
    private email: string;
    private peso: number;
    private altura: number;
    private casado: boolean;
    private profissao: string;
    
    addNome(nome: string): PessoaBuilder {
      this.nome = nome;
      return this;
    }
    addIdade(idade: number): PessoaBuilder {
      this.idade = idade;
      return this;
    }
    addCidade(cidade: string): PessoaBuilder {
      this.cidade = cidade;
      return this;
    }
    addBairro(bairro: string): PessoaBuilder {
      this.bairro = bairro;
      return this;
    }
    addTelefone(telefone: string): PessoaBuilder {
      this.telefone = telefone;
      return this;
    }
    addEmail(email: string): PessoaBuilder {
      this.email = email;
      return this;
    }
    addPeso(peso: number): PessoaBuilder {
      this.peso = peso;
      return this;
    }
    addAltura(altura: number): PessoaBuilder {
      this.altura = altura;
      return this;
    }
    addCasado(casado: boolean): PessoaBuilder {
      this.casado = casado;
      return this;
    }
    addProfissao(profissao: string): PessoaBuilder {
      this.profissao = profissao;
      return this;
    }
    build(): Pessoa {
      return new Pessoa(this.nome, this.idade, this.cidade, this.bairro, this.telefone, 
        this.email, this.peso, this.altura, this.casado, this.profissao);
    }
    }
    

    Java:

    public class PessoaBuilderImpl implements PessoaBuilder {
    
    private String nome;
    private Integer idade;
    private String cidade;
    private String bairro;
    private String telefone;
    private String email;
    private BigDecimal peso;
    private BigDecimal altura;
    private boolean casado;
    private String profissao;
    
    @Override
    public PessoaBuilder nome(String nome) {
      this.nome = nome;
      return this;
    }
    
    @Override
    public PessoaBuilder idade(Integer idade) {
      this.idade = idade;
      return this;
    }
    
    @Override
    public PessoaBuilder cidade(String cidade) {
      this.cidade = cidade;
      return this;
    }
    
    @Override
    public PessoaBuilder bairro(String bairro) {
      this.bairro = bairro;
      return this;
    }
    
    @Override
    public PessoaBuilder telefone(String telefone) {
      this.telefone = telefone;
      return this;
    }
    
    @Override
    public PessoaBuilder email(String email) {
      this.email = email;
      return this;
    }
    
    @Override
    public PessoaBuilder peso(BigDecimal peso) {
      this.peso = peso;
      return this;
    }
    
    @Override
    public PessoaBuilder altura(BigDecimal altura) {
      this.altura = altura;
      return this;
    }
    
    @Override
    public PessoaBuilder casado(boolean casado) {
      this.casado = casado;
      return this;
    }
    
    @Override
    public PessoaBuilder profissao(String profissao) {
      this.profissao = profissao;
      return this;
    }
    
    @Override
    public Pessoa build() {
    
      var pessoa = new Pessoa();
    
      pessoa.setNome(this.nome);
      pessoa.setIdade(this.idade);
      pessoa.setCidade(this.cidade);
      pessoa.setBairro(this.bairro);
      pessoa.setTelefone(this.telefone);
      pessoa.setAltura(this.altura);
      pessoa.setPeso(this.peso);
      pessoa.setEmail(this.email);
      pessoa.setCasado(this.casado);
      pessoa.setProfissao(this.profissao);
    
      return pessoa;
    }
    }
    

    Classe do objeto complexo:

    Typescript:

    class Pessoa {
    nome?: string;
    idade?: number;
    cidade?: string;
    bairro?: string;
    telefone?: string;
    email?: string;
    peso?: number;
    altura?: number;
    casado?: boolean;
    profissao?: string;
    
    constructor(nome?: string, idade?: number, cidade?: string, bairro?: string, 								  
                telefone?: string, email?: string, peso?: number, altura?: number,
                casado?: boolean, profissao?: string) {
      this.nome = nome;
      this.idade = idade;
      this.cidade = cidade;
      this.bairro = bairro;
      this.telefone = telefone;
      this.email = email;
      this.peso = peso;
      this.altura = altura;
      this.casado = casado;
      this.profissao = profissao;
    }
    
    //método estático para retornar uma instância do builder
    public static PessoaBuilder() {
      return new PessoaBuilderImpl();
    }
    
    }
    

    Java:

    public class Pessoa {
    
    private String nome;
    private Integer idade;
    private String cidade;
    private String bairro;
    private String telefone;
    private String email;
    private BigDecimal peso;
    private BigDecimal altura;
    private boolean casado;
    private String profissao;
    
    // ------------------------------------------------------------------------------------------------
    
    public String getNome() {
      return nome;
    }
    
    public void setNome(String nome) {
      this.nome = nome;
    }
    
    public Integer getIdade() {
      return idade;
    }
    
    public void setIdade(Integer idade) {
      this.idade = idade;
    }
    
    public String getCidade() {
      return cidade;
    }
    
    public void setCidade(String cidade) {
      this.cidade = cidade;
    }
    
    public String getBairro() {
      return bairro;
    }
    
    public void setBairro(String bairro) {
      this.bairro = bairro;
    }
    
    public String getTelefone() {
      return telefone;
    }
    
    public void setTelefone(String telefone) {
      this.telefone = telefone;
    }
    
    public String getEmail() {
      return email;
    }
    
    public void setEmail(String email) {
      this.email = email;
    }
    
    public BigDecimal getPeso() {
      return peso;
    }
    
    public void setPeso(BigDecimal peso) {
      this.peso = peso;
    }
    
    public BigDecimal getAltura() {
      return altura;
    }
    
    public void setAltura(BigDecimal altura) {
      this.altura = altura;
    }
    
    public boolean isCasado() {
      return casado;
    }
    
    public void setCasado(boolean casado) {
      this.casado = casado;
    }
    
    public String getProfissao() {
      return profissao;
    }
    
    public void setProfissao(String profissao) {
      this.profissao = profissao;
    }
    
    // ------------------------------------------------------------------------------------------------
    
    
    //método estático para retornar uma instância do builder
    public static PessoaBuilder builder(): PessoaBuilder {
      return new PessoaBuilderImpl();
    }
     
    @Override
    public String toString() {
       
      String casado = this.casado ? "Sim" : "Não";
       
      return "Nome: " + this.nome + "\n" +
          "Idade: " + this.idade + "\n" +
          "Cidade: " + this.cidade + "\n" +
          "Bairro: " + this.bairro + "\n" +
          "Telefone: " + this.telefone + "\n" +
          "Email: " + this.email + "\n" +
          "Peso: " + this.peso + "\n" +
          "Altura" + this.altura + "\n" +
          "Casado: " + casado + "\n" + 
          "Profissão: " + profissao + "\n";
    }
    }
    

    Instanciação do objeto complexo:

    Typescript:

    const pessoa: Pessoa = Pessoa
    .builder()
    .addNome("Pedro Lucas")
    .addIdade(21)
    .addCidade("Icó")
    .addBairro("Centro")
    .addTelefone("4002-8922")
    .addEmail("pedrolucas@gmail.com")
    .addPeso(83.5)
    .addAltura(1.83)
    .addCasado(true)
    .addProfissao("Pedreiro")
    .build();
    

    Resultado no console da aplicação:

    Pessoa {
     nome: 'Pedro Lucas',
     idade: 21,
     cidade: 'Icó',
     bairro: 'Centro',
     telefone: '4002-8922',
     email: 'pedrolucas@gmail.com',
     peso: 83.5,
     altura: 1.83,
     casado: true,
     profissao: 'Pedreiro'
    }
    

    Java:

    public class Main {
    
    public static void main(String[] args) {
    
      //Instanciando o objeto complexo com builder
      Pessoa pessoa = Pessoa
          .builder()
          .nome("Pedro Lucas")
          .idade(21)
          .cidade("Icó")
          .bairro("Centro")
          .telefone("4002-8922")
          .email("pedrolucas@gmail.com")
          .peso(BigDecimal.valueOf(83.5))
          .altura(BigDecimal.valueOf(1.83))
          .casado(true)
          .profissao("Pedreiro")
          .build();
    
      System.out.println(pessoa);
    
    }
    }
    

    Resultado no console da aplicação:

    Nome: Pedro Lucas
    Idade: 21
    Cidade: Icó
    Bairro: Centro
    Telefone: 4002-8922
    Email: pedrolucas@gmail.com
    Peso: 83.5
    Altura1.83
    Casado: Sim
    Profissão: Pedreiro
    

    Como dito anteriormente, a instanciação é feita passo-a-passo. Perceba também que é possível também fazer encadeamento, isso graças aos métodos nas classes que implementam a interface de builder retornarem suas próprias instâncias (o "return this").

    Bônus: Utilizando o Builder no Java com Lombok de maneira super fácil

    Além disso, no Java é possível fazer uso deste padrão de uma forma ainda mais fácil, com o Lombok. O Lombok é uma biblioteca com anotations (anotações) para reduzir o uso de códigos repetitivos (como por exemplo, getters, setters, construtores, toString, hashCode, equals, entre outros). Vejamos o código abaixo:

    @Data
    @Builder
    public class Pessoa {
    
    private String nome;
    private Integer idade;
    private String cidade;
    private String bairro;
    private String telefone;
    private String email;
    private BigDecimal peso;
    private BigDecimal altura;
    private boolean casado;
    private String profissao;
    
    }
    

    Primeiramente, usamos a anotação @Data, que gera getters, setters, hashCode, equals e entre outras coisas de forma automática. Logo em seguida, usamos o @Builder para que ele use este padrão de forma automática. E pronto! Já está feito! Incrível, não é?

    Vejamos abaixo o código de instanciação desta classe (Pessoa):

    public class Main {
    
    public static void main(String[] args) {
    
      Pessoa pessoa = Pessoa
          .builder()
          .nome("Pedro Lucas")
          .idade(21)
          .cidade("Icó")
          .bairro("Centro")
          .telefone("4002-8922")
          .email("pedrolucas@gmail.com")
          .peso(BigDecimal.valueOf(83.5))
          .altura(BigDecimal.valueOf(1.83))
          .casado(true)
          .profissao("Pedreiro")
          .build();
    
      System.out.println(pessoa);
    
    }
    }
    

    Resultado no console:

    Pessoa(nome=Pedro Lucas, idade=21, cidade=Icó, bairro=Centro, telefone=4002-8922, email=pedrolucas@gmail.com, peso=83.5, altura=1.83, casado=true, profissao=Pedreiro)
    
    Compartilhe
    Comentários (2)
    Higor Lima
    Higor Lima - 12/08/2023 18:15

    Concordo Giovanni, a economia de código chega a ser um absurdo!

    Giovanni Rozza
    Giovanni Rozza - 12/08/2023 18:12

    Lombok é uma ferramenta e tanto. Uso demais ela.