Ir ao conteúdo

Padrão de Projeto Abstract Factory em PHP com exemplo

O Padrão de Projeto Abstract Factory é utilizado quando queremos criar uma família e objetos relacionados sem necessariamente especificar suas classes concretas. Ou seja, criarmos diferentes tipos de contratos utilizando métodos abstratos ou interfaces, para diferentes produtos para garantir que as classes concretas não serão chamadas diretamente. Isso entra em consonância com o SRP (Single Responsibility Principle).

Diagrama UML Abstract Factory Pattern
Diagrama UML Abstract Factory Pattern

O diagrama acima mostra a relação entre as Factories concretas e os produtos concretos que podem ser gerados. 

O Padrão Abstract Factory se relaciona muito bem com o padrão Factory Method. No artigo que falei sobre Factory Method, utilizei um exemplo de criação de objetos do tipo instrumentos musicais. Deste modo, para você conseguir fazer uma assimilação bacana, vou criar uma fábrica de objetos do tipo instrumentos musicais utilizando fábricas abstratas. Com isso, vai te ajudar a memorizar estes conceitos e não fazer confusão com o Factory Method.

Vou fazer melhor, como o diagrama deste pattern parece um pouco complicado à primeira vista, vou representar esta Factory de Instrumentos musicais seguindo exatamente o que representa o Diagrama UML original deste Pattern.

Para dar uma noção dos recursos que o PHP oferece, ao invés de utilizar interface, vou fazer utilizando classes e métodos abstratos (vai me ajudar a economizar códigos para este exemplo também 😅).  Isso vai te ajudar a aumentar seu repertório de possibilidades de implementações.

Diagrama UML Abstract Factory Pattern com exemplo
Diagrama UML com exemplo

Como você pode ver no diagrama, temos duas famílias de instrumentos musicais, os normais e os elétricos. Dentre os produtos vamos ter Pianos e Violões, normais e elétricos para ambos.  Agora vai deixar claro a relação que há entre as factories e a família de instrumentos (objetos concretos). Veja nos códigos a seguir como fica o exemplo do Padrão de Projeto Abstract Factory utilizando o PHP.

Criando as classes Factory

Esta classe abstrata, possui seus métodos abstratos, deste modo, todas as filhas que estenderem desta, terão que obrigatoriamente implementar estes métodos, seguindo esta assinatura.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory;

use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar\Guitar;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano\Piano;

abstract class MusicalInstrumentFactory
{
    abstract public function buildGuitar(string $brand): Guitar;

    abstract public function buildPiano(string $brand): Piano;
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory;

use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar\Guitar;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar\NormalGuitar;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano\NormalPiano;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano\Piano;

class NormalMusicalInstrumentFactory extends MusicalInstrumentFactory
{
    public function buildGuitar(string $brand): Guitar
    {
        return new NormalGuitar($brand);
    }

    public function buildPiano(string $brand): Piano
    {
        return new NormalPiano($brand);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory;

use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar\ElectricGuitar;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar\Guitar;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano\ElectricPiano;
use Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano\Piano;

class ElectricMusicalInstrumentFactory extends MusicalInstrumentFactory
{
    public function buildGuitar(string $brand): Guitar
    {
        return new ElectricGuitar($brand);
    }

    public function buildPiano(string $brand): Piano
    {
        return new ElectricPiano($brand);
    }
}

Criando os Produtos

Família de produtos do tipo Piano

Fiz a classe Piano e Guitarra também abstratas, para reutilizar o método getBrand. E para seguir a proposta do artigo, ao invés de criar um contrato com interface para o getModel (que não agregaria muito para este exemplo), também defini-o como abstrato para forçar suas classes filhas a implementarem concretamente.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano;

abstract class Piano
{
    private string $brand;

    public function __construct(string $brand)
    {
        $this->brand = $brand;
    }

    public function getBrand(): string
    {
        return $this->brand;
    }
    
    abstract public function getModel(): string;
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano;

class NormalPiano extends Piano
{
    public function getModel(): string
    {
        return sprintf('%s Normal Piano', parent::getBrand());
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Piano;

class ElectricPiano extends Piano
{
    public function getModel(): string
    {
        return sprintf('%s Electric Piano', parent::getBrand());
    }
}

Família de produtos do tipo Violão

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar;

abstract class Guitar
{
    private string $brand;

    public function __construct(string $brand)
    {
        $this->brand = $brand;
    }

    public function getBrand(): string
    {
        return $this->brand;
    }
    
    abstract public function getModel(): string;
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar;

class NormalGuitar extends Guitar
{
    public function getModel(): string
    {
        return sprintf('%s Normal Guitar', parent::getBrand());
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\AbstractFactory\Products\Guitar;

class ElectricGuitar extends Guitar
{
    public function getModel(): string
    {
        return sprintf('%s Electric Guitar', parent::getBrand());
    }
}

Para mostrar o padrão Abstract Factory em ação, vamos criar duas classes de testes distintas para cada tipo, elétrico e normal.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Creational\AbstractFactory;

use Growthdev\DesignPatterns\Creational\AbstractFactory\ElectricMusicalInstrumentFactory;
use PHPUnit\Framework\TestCase;

final class ElectricMusicalInstrumentFactoryTest extends TestCase
{
    public function testCanBuildElectricGuitar(): void
    {
        $factory = new ElectricMusicalInstrumentFactory();
        $gianniniGuitar = $factory->buildGuitar("Giannini");
        $tagimaGuitar = $factory->buildGuitar("Tagima");
        $taylorGuitar = $factory->buildGuitar("Taylor");

        $this->assertEquals("Giannini Electric Guitar", $gianniniGuitar->getModel());
        $this->assertEquals("Tagima Electric Guitar", $tagimaGuitar->getModel());
        $this->assertEquals("Taylor Electric Guitar", $taylorGuitar->getModel());
    }

    public function testCanBuildElectricPiano(): void
    {
        $factory = new ElectricMusicalInstrumentFactory();
        $yamahaPiano = $factory->buildPiano("Yamaha");
        $korgPiano = $factory->buildPiano("Korg");

        $this->assertEquals("Yamaha Electric Piano", $yamahaPiano->getModel());
        $this->assertEquals("Korg Electric Piano", $korgPiano->getModel());
    }


}
Resultado teste Fábrica Instrumentos Musicais Elétricos
Resultado teste Fábrica Instrumentos Musicais Elétricos
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Creational\AbstractFactory;

use Growthdev\DesignPatterns\Creational\AbstractFactory\NormalMusicalInstrumentFactory;
use PHPUnit\Framework\TestCase;

final class NormalMusicalInstrumentFactoryTest extends TestCase
{
    public function testCanBuildNormalGuitar(): void
    {
        $factory = new NormalMusicalInstrumentFactory();
        $gianniniGuitar = $factory->buildGuitar("Giannini");
        $tagimaGuitar = $factory->buildGuitar("Tagima");
        $taylorGuitar = $factory->buildGuitar("Taylor");

        $this->assertEquals("Giannini Normal Guitar", $gianniniGuitar->getModel());
        $this->assertEquals("Tagima Normal Guitar", $tagimaGuitar->getModel());
        $this->assertEquals("Taylor Normal Guitar", $taylorGuitar->getModel());
    }

    public function testCanBuildNormalPiano(): void
    {
        $factory = new NormalMusicalInstrumentFactory();
        $bosendorferPiano = $factory->buildPiano("Bosendorfer");
        $fazioliPiano = $factory->buildPiano("Fazioli");

        $this->assertEquals("Bosendorfer Normal Piano", $bosendorferPiano->getModel());
        $this->assertEquals("Fazioli Normal Piano", $fazioliPiano->getModel());
    }
}
Resultado teste Fábrica Instrumentos Musicais Normais
Resultado teste Fábrica Instrumentos Musicais Normais

O padrão Abstract Factory, tem uma característica que pode gerar um pouco de confusão em relação ao padrão Factory Method. Mas, a diferença principal entre ambos é que o padrão Factory Method expõe um método ao cliente para criar objetos, enquanto no Abstract Factory ele expõe uma família de objetos relacionados que podem consistir nesses métodos de Factory.

Veja também que simplesmente entender como representar um padrão de Projeto não é suficiente para garantir uma ótima qualidade de código. Tenha sempre em mente os demais princípios que nos ajudam a garantir um mínimo de qualidade, como por exemplo, o uso do SOLID. Perceba que a linha, ao utilizar este pattern, é muito tênue em ferir, por exemplo, o Open Closed Principle. Qualquer pattern, analise muito bem antes de utilizar. Mas, utilizando conscientemente você vai ter uma vida longa e feliz 😇.

Ainda vamos ver muito outros artigos com muitos exemplos práticos sobre padrões de projetos. Por hora, espero que você tenha aproveitado bastante este artigo. Deixe aqui seu comentário e compartilhe este artigo para que outras pessoas possam se beneficiar deste conteúdo assim como você. Até o próximo artigo!

Confiança Sempre!!!

Fontes:

Publicado emDesign PatternPadrões de ProjetosPHP

Seja o primeiro a comentar

Deixe um comentário

O seu endereço de e-mail não será publicado.