O Padrão de Projeto Builder é muito útil quando você tem objetos complexos que precisam ser criados parte a parte. Ou seja, você tem a estrutura da classe e consegue criar os objetos como se fossem um passo a passo.
O padrão Builder é composto por quatro componentes básicos que são: a Interface (ou classe abstrata) Builder, o Concrete Builder (construtor concreto), o Director (Diretor) e o Product (produto).
Resumidamente Director é responsável por chamar o método de construção do Builder, que chama sua implementações especializadas, Concrete Builder, que possui em sua implementação a lógica para construir nossa classe final que é o Product;
Diagrama UML do Padrão de Projeto Builder
No seu dia a dia como programador, você vai encontrar situações em que você precisará criar uma classe que vai representar a construção de um determinado objeto, só que esse objeto terá pequenas partes, o que os tornam complexos, no sentido de muitas partes. Haverá também situações em que você vai precisar criar uma instância desse objeto, porém não vai precisar carregar todas as suas características.
Como normalmente era feita esta implementação? Criava-se um objeto com um construtor enorme, com permissão para entradas com valores null, ou valores default também nulos. E na instanciação, desconsiderava o que precisava. Em algumas linguagens como Java, permite criar múltiplos construtores o que facilitava um pouco neste quesito, porém, utilizando o padrão Builder o resultado esperado é mais desejável.
Implementação do Padrão Builder Passo a Passo
Vou fazer neste artigo duas variações do padrão do Builder em PHP. Para exemplo, o nosso problema será criar um construtor de hambúrguer, pelo fato de um hambúrguer tem vários componentes, vários ingredientes, então fica fácil para criarmos este exemplo. No final do artigo vou mostrar outros exemplos práticos para que você consiga contextualizar melhor a importância de uso deste pattern.
Representação do produto
O próprio hambúrguer é o produto. Ele representa o modelo do produto que vamos construir
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
interface BurgerBuilderInterface
{
public function addBread(string $bread): self;
public function addPatty(string $patty): self;
public function addVeggies(string $veggies): self;
public function addSauces(string $saouces): self;
public function addWithExtraCheese(): self;
public function build(): Burger;
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
interface BurgerBuilderInterface
{
public function addBread(string $bread): self;
public function addPatty(string $patty): self;
public function addVeggies(string $veggies): self;
public function addSauces(string $saouces): self;
public function addWithExtraCheese(): self;
public function build(): Burger;
}
class BurgerBuilder implements BurgerBuilderInterface
{
private Burger $burger;
publicfunction__construct()
{
$this->burger = newBurger();
}
publicfunctionaddBread(string$bread): self
{
$this->burger->setBread($bread);
return$this;
}
publicfunctionaddPatty(string$patty): self
{
$this->burger->setPatty($patty);
return$this;
}
publicfunctionaddVeggies(string$veggies): self
{
$this->burger->setVeggies($veggies);
return$this;
}
publicfunctionaddSauces(string$sauces): self
{
$this->burger->setSauces($sauces);
return$this;
}
publicfunctionaddWithExtraCheese(): self
{
$this->burger->setWithExtraCheese(true);
return$this;
}
publicfunctionbuild(): Burger
{
return$this->burger;
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
class BurgerBuilder implements BurgerBuilderInterface
{
private Burger $burger;
public function __construct()
{
$this->burger = new Burger();
}
public function addBread(string $bread): self
{
$this->burger->setBread($bread);
return $this;
}
public function addPatty(string $patty): self
{
$this->burger->setPatty($patty);
return $this;
}
public function addVeggies(string $veggies): self
{
$this->burger->setVeggies($veggies);
return $this;
}
public function addSauces(string $sauces): self
{
$this->burger->setSauces($sauces);
return $this;
}
public function addWithExtraCheese(): self
{
$this->burger->setWithExtraCheese(true);
return $this;
}
public function build(): Burger
{
return $this->burger;
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
class BurgerBuilder implements BurgerBuilderInterface
{
private Burger $burger;
public function __construct()
{
$this->burger = new Burger();
}
public function addBread(string $bread): self
{
$this->burger->setBread($bread);
return $this;
}
public function addPatty(string $patty): self
{
$this->burger->setPatty($patty);
return $this;
}
public function addVeggies(string $veggies): self
{
$this->burger->setVeggies($veggies);
return $this;
}
public function addSauces(string $sauces): self
{
$this->burger->setSauces($sauces);
return $this;
}
public function addWithExtraCheese(): self
{
$this->burger->setWithExtraCheese(true);
return $this;
}
public function build(): Burger
{
return $this->burger;
}
}
Criando o Director
Segundo a solução proposta no livro GOF, temos que utilizar um personagem chamado Diretor, que é o cara que de fato “cria” o Product. Porém, no próximo exemplo vamos ver que esse participante do diagrama pode ser desconsiderado tranquilamente.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
final class BurgerDirector
{
public function buildBurger(BurgerBuilderInterface $builder): Burger
{
return $builder->build();
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder\Burger;
final class BurgerDirector
{
public function buildBurger(BurgerBuilderInterface $builder): Burger
{
return $builder->build();
}
}
Veja no Test que criamos a instância do Burger, e adicionamos as partes que compõem um hamburger de forma passo a passo, no segundo Test, você pode notar que podemos tranquilamente não utilizar partes para construir nosso objeto.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Tests\Creational\Builder\Burger;
use Growthdev\DesignPatterns\Creational\Builder\Burger\BurgerBuilder;
use Growthdev\DesignPatterns\Creational\Builder\Burger\BurgerDirector;
use PHPUnit\Framework\TestCase;
final class BurgerBuilderTest extends TestCase
{
public function testCanCreateBurger(): void
{
$burgerBuilder = (new BurgerBuilder())
->addBread("Brown Bread")
->addPatty("Beef")
->addVeggies("Pickles")
->addSauces("All")
->addWithExtraCheese();
$burgerDirector = new BurgerDirector();
$burger = $burgerDirector->buildBurger($burgerBuilder);
$this->assertEquals("Brown Bread", $burger->getBread());
$this->assertEquals("Beef", $burger->getPatty());
$this->assertEquals("Pickles", $burger->getVeggies());
$this->assertEquals("All", $burger->getSauces());
$this->assertTrue($burger->getWithExtraCheese());
}
public function testCanCreatePartialBurger(): void
{
$burgerBuilder = (new BurgerBuilder())
->addBread("Brown Bread")
->addPatty("Beef")
->addSauces("All");
$burgerDirector = new BurgerDirector();
$burger = $burgerDirector->buildBurger($burgerBuilder);
$this->assertEquals("Brown Bread", $burger->getBread());
$this->assertEquals("Beef", $burger->getPatty());
$this->assertEquals("All", $burger->getSauces());
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Tests\Creational\Builder\Burger;
use Growthdev\DesignPatterns\Creational\Builder\Burger\BurgerBuilder;
use Growthdev\DesignPatterns\Creational\Builder\Burger\BurgerDirector;
use PHPUnit\Framework\TestCase;
final class BurgerBuilderTest extends TestCase
{
public function testCanCreateBurger(): void
{
$burgerBuilder = (new BurgerBuilder())
->addBread("Brown Bread")
->addPatty("Beef")
->addVeggies("Pickles")
->addSauces("All")
->addWithExtraCheese();
$burgerDirector = new BurgerDirector();
$burger = $burgerDirector->buildBurger($burgerBuilder);
$this->assertEquals("Brown Bread", $burger->getBread());
$this->assertEquals("Beef", $burger->getPatty());
$this->assertEquals("Pickles", $burger->getVeggies());
$this->assertEquals("All", $burger->getSauces());
$this->assertTrue($burger->getWithExtraCheese());
}
public function testCanCreatePartialBurger(): void
{
$burgerBuilder = (new BurgerBuilder())
->addBread("Brown Bread")
->addPatty("Beef")
->addSauces("All");
$burgerDirector = new BurgerDirector();
$burger = $burgerDirector->buildBurger($burgerBuilder);
$this->assertEquals("Brown Bread", $burger->getBread());
$this->assertEquals("Beef", $burger->getPatty());
$this->assertEquals("All", $burger->getSauces());
}
}
Padrão Builder simplificado
Observando bem os participantes, veja que temos apenas propriedades que geram as características do modelo e os métodos para atribuir e retornar os seus valores. Com isso, você consegue facilmente modelar o Padrão de Projeto Builder de forma diferente e ter o mesmo resultado.
No Livro Java Efetivo do Joshua Bloch ele trás um exemplo super inteligente de uso do Padrão Builder utilizando em combinação com o padrão Static Method Factory (uma alternativa ao uso dos construtores) com uma peculiaridade de utilizar uma classe aninhada.
Como o PHP não permite este recurso, de classes aninhadas, fiz uma variação utilizando Trait e classe anônima, que vai produzir o mesmo efeito. Vamos encapsular as propriedades e métodos e acesso aos valores na classe BurderTrait para ser utilizado, tanto na classe Burger quanto na BurguerBuilder
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
interface BurgetInterface
{
public function getBread(): string;
public function getPatty(): string;
public function getVeggies(): string;
public function getSauces(): string;
public function getWithExtraCheese(): bool;
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
interface BurgetInterface
{
public function getBread(): string;
public function getPatty(): string;
public function getVeggies(): string;
public function getSauces(): string;
public function getWithExtraCheese(): bool;
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
interface BurgerBuilderInterface
{
public function addBread(string $bread): self;
public function addPatty(string $patty): self;
public function addVeggies(string $veggies): self;
public function addSauces(string $saouces): self;
public function addWithExtraCheese(): self;
public function build(): Burger;
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
interface BurgerBuilderInterface
{
public function addBread(string $bread): self;
public function addPatty(string $patty): self;
public function addVeggies(string $veggies): self;
public function addSauces(string $saouces): self;
public function addWithExtraCheese(): self;
public function build(): Burger;
}
Definições prontas, podemos juntar tudo na classe Burger e utilizar o Static Method Factory para implementar os recursos para o Builder. Para isso, vamos chamar a classe anônima através deste método para efetuar a ligação com a classe concreta, Burger.
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
class Burger implements BurgetInterface
{
use BurgerTrait;
public static function builder(): BurgerBuilderInterface
{
return new class implements BurgerBuilderInterface, BurgetInterface {
use BurgerTrait;
public function addBread(string $bread): self
{
$this->bread = $bread;
return $this;
}
public function addPatty(string $patty): self
{
$this->patty = $patty;
return $this;
}
public function addVeggies(string $veggies): self
{
$this->veggies = $veggies;
return $this;
}
public function addSauces(string $sauces): self
{
$this->sauces = $sauces;
return $this;
}
public function addWithExtraCheese(): self
{
$this->withExtraCheese = true;
return $this;
}
public function build(): Burger
{
return new Burger($this);
}
};
}
public function __construct(BurgetInterface $builder)
{
$this->bread = $builder->getBread();
$this->patty = $builder->getPatty();
$this->veggies = $builder->getVeggies();
$this->sauces = $builder->getSauces();
$this->withExtraCheese = $builder->getWithExtraCheese();
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Creational\Builder;
class Burger implements BurgetInterface
{
use BurgerTrait;
public static function builder(): BurgerBuilderInterface
{
return new class implements BurgerBuilderInterface, BurgetInterface {
use BurgerTrait;
public function addBread(string $bread): self
{
$this->bread = $bread;
return $this;
}
public function addPatty(string $patty): self
{
$this->patty = $patty;
return $this;
}
public function addVeggies(string $veggies): self
{
$this->veggies = $veggies;
return $this;
}
public function addSauces(string $sauces): self
{
$this->sauces = $sauces;
return $this;
}
public function addWithExtraCheese(): self
{
$this->withExtraCheese = true;
return $this;
}
public function build(): Burger
{
return new Burger($this);
}
};
}
public function __construct(BurgetInterface $builder)
{
$this->bread = $builder->getBread();
$this->patty = $builder->getPatty();
$this->veggies = $builder->getVeggies();
$this->sauces = $builder->getSauces();
$this->withExtraCheese = $builder->getWithExtraCheese();
}
}
Esta implementação ficou um pouco “verbosa”, porém, com o uso do Trait, deu para economizar boas linhas de código. Mas tudo indica que quando estivermos com o PHP 8.1, ao utilizarmos readonly nas propriedades, vamos conseguir garantir a imutabilidade sem tanto esforço.
Enfim, mas pode notar que o comportamento esperado para nossa solução permanece. Veja como ficou mais interessante desta forma:
E aí? De que forma você costuma implementar o Padrão de Projeto Builder? Você conhecia o padrão Static Method Factory? Deixa aqui seu comentário que ajudará bastante este site a disseminar conhecimento de qualidade e acessível a todos. Até o próximo artigo!
Confiança Sempre!!!
Fontes:
[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.
Olá! Sou Walmir, engenheiro de software com MBA em Engenharia de Software e o cérebro por trás do GrowthCode e autor do livro "Além do Código".
Se você acha que programação é apenas sobre escrever código, prepare-se para expandir seus horizontes. Aqui, nós vamos além do código e exploramos as interseções fascinantes entre tecnologia, negócios, artes e filosofia.
Você está em busca de crescimento na carreira? Quer se destacar em um mercado competitivo? Almeja uma vida mais rica em conhecimento e realização? Então você chegou ao lugar certo. No GrowthCode, oferecemos insights profundos, estratégias comprovadas e um toque de sabedoria filosófica para catalisar seu crescimento pessoal e profissional.
Seja o primeiro a comentar