Design Patterns
Criacionais
Padrão de Projeto: Builder

Padrão de Projeto: Builder

Propósito

O Builder é um padrão de projeto criacional que permite a você construir objetos complexos passo a passo. O padrão permite que você produza diferentes tipos e representações de um objeto usando o mesmo código de construção.

Problema

Imagine um objeto complexo que necessite de uma inicialização passo a passo trabalhosa de muitos campos e objetos agrupados. Tal código de inicialização fica geralmente enterrado dentro de um construtor monstruoso com vários parâmetros. Ou pior: espalhado por todo o código cliente.

Por exemplo, vamos pensar sobre como criar um objeto Casa. Para construir uma casa simples, você precisa construir quatro paredes e um piso, instalar uma porta, encaixar um par de janelas, e construir um teto. Mas e se você quiser uma casa maior e mais iluminada, com um jardim e outras miudezas (como um sistema de aquecimento, encanamento, e fiação elétrica)?

A solução mais simples é estender a classe base Casa e criar um conjunto de subclasses para cobrir todas as combinações de parâmetros. Mas eventualmente você acabará com um número considerável de subclasses. Qualquer novo parâmetro, tal como o estilo do pórtico, irá forçá-lo a aumentar essa hierarquia cada vez mais.

Há outra abordagem que não envolve a propagação de subclasses. Você pode criar um construtor gigante diretamente na classe Casa base com todos os possíveis parâmetros que controlam o objeto casa. Embora essa abordagem realmente elimine a necessidade de subclasses, ela cria outro problema: na maioria dos casos a maioria dos parâmetros não será usada, tornando as chamadas do construtor em algo feio de se ver.

Solução

O padrão Builder sugere que você extraia o código de construção do objeto para fora de sua própria classe e mova ele para objetos separados chamados builders. O padrão organiza a construção de objetos em uma série de etapas (construirParedes, construirPorta, etc.). Para criar um objeto você executa uma série de etapas em um objeto builder. A parte importante é que você não precisa chamar todas as etapas. Você chama apenas aquelas etapas que são necessárias para a produção de uma configuração específica de um objeto.

Estrutura

  1. Builder: Declara etapas de construção do produto que são comuns a todos os tipos de builders.
  2. Builders Concretos: Provêm diferentes implementações das etapas de construção.
  3. Produtos: São os objetos resultantes. Produtos construídos por diferentes builders não precisam pertencer a mesma interface ou hierarquia da classe.
  4. Diretor: Define a ordem na qual as etapas de construção são chamadas, então você pode criar e reutilizar configurações específicas de produtos.
  5. Cliente: Deve associar um dos objetos builders com o diretor.

Exemplo em PHP

Produto

class Carro {
    public $assentos;
    public $motor;
    public $computadorDeBordo;
    public $gps;
 
    public function especificacoes() {
        return "Carro com {$this->assentos} assentos, motor {$this->motor}, computador de bordo: " . ($this->computadorDeBordo ? 'sim' : 'não') . ", GPS: " . ($this->gps ? 'sim' : 'não') . ".";
    }
}

Builder

interface Builder {
    public function reset();
    public function setAssentos($numero);
    public function setMotor($motor);
    public function setComputadorDeBordo($computadorDeBordo);
    public function setGPS($gps);
}

Builders Concretos

class CarroBuilder implements Builder {
    private $carro;
 
    public function __construct() {
        $this->reset();
    }
 
    public function reset() {
        $this->carro = new Carro();
    }
 
    public function setAssentos($numero) {
        $this->carro->assentos = $numero;
    }
 
    public function setMotor($motor) {
        $this->carro->motor = $motor;
    }
 
    public function setComputadorDeBordo($computadorDeBordo) {
        $this->carro->computadorDeBordo = $computadorDeBordo;
    }
 
    public function setGPS($gps) {
        $this->carro->gps = $gps;
    }
 
    public function getProduto() {
        $produto = $this->carro;
        $this->reset();
        return $produto;
    }
}

Diretor

class Diretor {
    private $builder;
 
    public function setBuilder(Builder $builder) {
        $this->builder = $builder;
    }
 
    public function construirCarroEsportivo() {
        $this->builder->reset();
        $this->builder->setAssentos(2);
        $this->builder->setMotor('V8');
        $this->builder->setComputadorDeBordo(true);
        $this->builder->setGPS(true);
    }
 
    public function construirSUV() {
        $this->builder->reset();
        $this->builder->setAssentos(5);
        $this->builder->setMotor('V6');
        $this->builder->setComputadorDeBordo(true);
        $this->builder->setGPS(true);
    }
}

Código Cliente

$diretor = new Diretor();
$builder = new CarroBuilder();
$diretor->setBuilder($builder);
 
echo "Construindo um carro esportivo:\n";
$diretor->construirCarroEsportivo();
$carro = $builder->getProduto();
echo $carro->especificacoes() . "\n";
 
echo "Construindo um SUV:\n";
$diretor->construirSUV();
$carro = $builder->getProduto();
echo $carro->especificacoes() . "\n";

Exemplo em Node.js

Produto

class Carro {
    constructor() {
        this.assentos = 0;
        this.motor = '';
        this.computadorDeBordo = false;
        this.gps = false;
    }
 
    especificacoes() {
        return `Carro com ${this.assentos} assentos, motor ${this.motor}, computador de bordo: ${this.computadorDeBordo ? 'sim' : 'não'}, GPS: ${this.gps ? 'sim' : 'não'}.`;
    }
}

Builder

class Builder {
    reset() {
        throw new Error("Método 'reset()' deve ser implementado.");
    }
 
    setAssentos(numero) {
        throw new Error("Método 'setAssentos()' deve ser implementado.");
    }
 
    setMotor(motor) {
        throw new Error("Método 'setMotor()' deve ser implementado.");
    }
 
    setComputadorDeBordo(computadorDeBordo) {
        throw new Error("Método 'setComputadorDeBordo()' deve ser implementado.");
    }
 
    setGPS(gps) {
        throw new Error("Método 'setGPS()' deve ser implementado.");
    }
}

Builders Concretos

class CarroBuilder extends Builder {
    constructor() {
        super();
        this.reset();
    }
 
    reset() {
        this.carro = new Carro();
    }
 
    setAssentos(numero) {
        this.carro.assentos = numero;
    }
 
    setMotor(motor) {
        this.carro.motor = motor;
    }
 
    setComputadorDeBordo(computadorDeBordo) {
        this.carro.computadorDeBordo = computadorDeBordo;
    }
 
    setGPS(gps) {
        this.carro.gps = gps;
    }
 
    getProduto() {
        const produto = this.carro;
        this.reset();
        return produto;
    }
}

Diretor

class Diretor {
    setBuilder(builder) {
        this.builder = builder;
    }
 
    construirCarroEsportivo() {
        this.builder.reset();
        this.builder.setAssentos(2);
        this.builder.setMotor('V8');
        this.builder.setComputadorDeBordo(true);
        this.builder.setGPS(true);
    }
 
    construirSUV() {
        this.builder.reset();
        this.builder.setAssentos(5);
        this.builder.setMotor('V6');
        this.builder.setComputadorDeBordo(true);
        this.builder.setGPS(true);
    }
}

Código Cliente

const diretor = new Diretor();
const builder = new CarroBuilder();
diretor.setBuilder(builder);
 
console.log("Construindo um carro esportivo:");
diretor.construirCarroEsportivo();
let carro = builder.getProduto();
console.log(carro.especificacoes());
 
console.log("Construindo um SUV:");
diretor.construirSUV();
carro = builder.getProduto();
console.log(carro.especificacoes());

Aplicabilidade

  • Use o padrão Builder para se livrar de um “construtor telescópico”.
  • Use o padrão Builder quando você quer que seu código seja capaz de criar diferentes representações do mesmo produto (por exemplo, casas de pedra e madeira).
  • Use o Builder para construir árvores Composite ou outros objetos complexos.

Prós e Contras

Prós

  • Você pode construir objetos passo a passo, adiar as etapas de construção ou rodar etapas recursivamente.
  • Você pode reutilizar o mesmo código de construção quando construindo várias representações de produtos.
  • Princípio de responsabilidade única: você pode isolar um código de construção complexo da lógica de negócio do produto.

Contras

  • A complexidade geral do código aumenta uma vez que o padrão exige criar múltiplas classes novas.

Relações com outros padrões

  • Muitos projetos começam usando o Factory Method e evoluem para o Abstract Factory, Prototype, ou Builder.
  • O Builder foca em construir objetos complexos passo a passo. O Abstract Factory se especializa em criar famílias de objetos relacionados.
  • Você pode usar o Builder quando criar árvores Composite complexas porque você pode programar suas etapas de construção para trabalhar recursivamente.
  • Você pode combinar o Builder com o Bridge: a classe diretor tem um papel de abstração, enquanto que diferentes construtores agem como implementações.

Este guia fornece uma visão abrangente do padrão Builder, ilustrando sua aplicação com exemplos em PHP e Node.js. Ele é essencial para criar sistemas flexíveis e extensíveis, permitindo a construção de objetos complexos de maneira desacoplada e seguindo os princípios de design orientado a objetos.