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
- Builder: Declara etapas de construção do produto que são comuns a todos os tipos de builders.
- Builders Concretos: Provêm diferentes implementações das etapas de construção.
- Produtos: São os objetos resultantes. Produtos construídos por diferentes builders não precisam pertencer a mesma interface ou hierarquia da classe.
- 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.
- 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.