Padrões de Projetos - Builder
Afluentes: Modelos, métodos e técnicas da engenharia de software
Definição
O padrão de projeto Builder (ou Construtor em português) é um padrão criacional que separa a construção de um objeto complexo de sua representação final. Ele permite que o mesmo processo de construção crie diferentes representações do objeto, facilitando a criação de objetos com muitas opções ou parâmetros, sem sobrecarregar o construtor com muitos argumentos.
Objetivo
O principal objetivo do Builder é fornecer uma maneira flexível e controlada de criar objetos complexos, onde o processo de criação pode ser dividido em etapas, cada uma configurando uma parte específica do objeto. Isso é particularmente útil para objetos que têm muitos atributos opcionais ou para situações em que a criação envolve um processo complicado.
Componentes principais
- Builder (Construtor): Define uma interface que descreve como construir as diferentes partes de um objeto complexo.
- ConcreteBuilder (Construtor Concreto): Implementa a interface Builder e constrói as partes específicas do produto.
- Product (Produto): O objeto final que será construído.
- Director (Diretor): Opcionalmente, o Director é responsável por gerenciar o processo de construção e garantir que as diferentes partes do objeto sejam criadas em uma ordem específica.
Quando usar o Builder
- Quando a criação de um objeto é complicada e envolve muitos parâmetros opcionais.
- Quando o objeto é imutável, mas tem muitas configurações que precisam ser definidas no momento da construção.
- Quando há várias representações possíveis de um objeto (por exemplo, diferentes variações do mesmo tipo de produto).
Exemplo em Java
Vamos pegar nosso exemplo que já trabalhamos antes, uma classe Enemy
para os inimigos em um jogo digital. Essa classe vários parâmetros opcionais, como posição x e y, pontos de experiência inicial (xp) e pontos de vida (hp). Em vez de ter um construtor com muitos parâmetros, podemos usar o padrão Builder para facilitar a construção do objeto.
Nossa classe Enemy com builder
public class Enemy {
private int id; // id do inimigo no jogo
private int x; // posição x do inimigo
private int y; // posição y do inimigo
private int xp; // pontos de experiência
private int hp; // pontos de vida
private static int counter = 0;
// Consrutor privado para forçar a criação via Builder
private Enemy(EnemyBuilder builder) {
this.id = Enemy.counter++;
this.x = builder.x;
this.y = builder.y;
this.xp = builder.xp;
this.hp = builder.hp;
}
public static class EnemyBuilder {
private int x;
private int y;
private int xp;
private int hp;
public EnemyBuilder setPosition(int x, int y) {
this.x = x;
this.y = y;
return this;
}
public EnemyBuilder setX(int x) {
this.x = x;
return this;
}
public EnemyBuilder setY(int y) {
this.y = y;
return this;
}
public EnemyBuilder setXp(int xp) {
this.xp = xp;
return this;
}
public EnemyBuilder setHp(int hp) {
this.hp = hp;
return this;
}
public Enemy build() {
return new Enemy(this);
}
}
public void draw() {
System.out.println("Enemy " + id + " at (" + x + ", " + y + "): XP: " + xp + ", HP:" + hp);
}
}
Nossa classe principal que utiliza o builder
public class Main {
public static void main(String[] args) {
// Criando um objeto do tipo Enemy
Enemy enemy = new Enemy.EnemyBuilder()
.setPosition(100, 200)
.setXp(34)
.setHp(100)
.build();
enemy.draw();
}
}
Explicação
- Produto (Enemy): A classe Enemy é o objeto que será construído. Ela tem muitos atributos opcionais (posição, pontos de experiência e pontos de vida). O construtor de Enemy é privado para garantir que ele só possa ser criado pelo
EnemyBuilder
. - Builder (
EnemyBuilder
): A classe internaEnemyBuilder
tem métodos para configurar cada atributo do Enemy. Esses métodos retornam o próprio builder (comreturn this;
), permitindo encadear chamadas (técnica chamada de fluent interface). - Método
build()
: O métodobuild()
noEnemyBuilder
é o responsável por criar o objeto Enemy final, passando obuilder
como argumento para o construtor privado de Enemy. - Cliente: O código no
main
demonstra como usar oEnemyBuilder
para configurar um objeto Enemy e depois construí-lo.
Vantagens do Builder
- Código mais legível: Permite a criação de objetos complexos com código que é mais fácil de entender, especialmente quando há muitos parâmetros opcionais.
- Flexibilidade: O processo de construção pode ser personalizado ou dividido em várias etapas.
- Imutabilidade: Pode ser usado para construir objetos imutáveis, pois o objeto final é completamente configurado antes de ser criado.
- Evitam construtores longos: Em vez de usar um construtor com muitos parâmetros, o Builder permite definir parâmetros de maneira flexível.
Desvantagens do Builder
- Mais código: Implementar o padrão Builder pode resultar em mais classes e código, o que pode parecer um overhead em casos simples.
- Necessidade de configuração: Para objetos relativamente simples, a configuração adicional do Builder pode não ser justificada.
Quando não usar o Builder
- Quando a criação de objetos é simples e envolve poucos parâmetros.
- Quando não há necessidade de criar diferentes variações do objeto.
Aplicações comuns
O padrão Builder é amplamente utilizado para a criação de objetos que possuem muitos parâmetros opcionais, como:
- Objetos de configuração em APIs e frameworks.
- Criação de objetos complexos como carros, casas, ou documentos.
- Em bibliotecas como o próprio Java, o padrão Builder é utilizado em classes como
StringBuilder
(embora tecnicamente não seja o padrão formal de design "Builder", ele oferece uma interface fluente similar).
Resumo
O padrão Builder é um padrão criacional que facilita a construção de objetos complexos, separando o processo de construção em etapas configuráveis. Ele permite criar diferentes representações de um objeto, tornando o código mais legível e flexível, especialmente quando há muitos atributos opcionais ou parâmetros.