Padrões de Projetos - Builder

De Aulas


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

  1. 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.
  2. Builder (EnemyBuilder): A classe interna EnemyBuilder tem métodos para configurar cada atributo do Enemy. Esses métodos retornam o próprio builder (com return this;), permitindo encadear chamadas (técnica chamada de fluent interface).
  3. Método build(): O método build() no EnemyBuilder é o responsável por criar o objeto Enemy final, passando o builder como argumento para o construtor privado de Enemy.
  4. Cliente: O código no main demonstra como usar o EnemyBuilder 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.