image

Bootcamps ilimitados + curso de inglês para sempre

80
%OFF
Higor Lima
Higor Lima12/08/2023 18:02
Compartilhe
WEX - End to End EngineeringRecomendados para vocêWEX - End to End Engineering

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

  • #TypeScript
  • #Java

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
Recomendados para você
Deal - Spring Boot e Angular (17+)
Cognizant - Arquitetura com Spring Boot e Cloud
Claro - Java com Spring Boot
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.

Recomendados para vocêWEX - End to End Engineering