Uma das das necessidades mais comuns do mundo da programação é a necessidade de criar instâncias ou conjuntos de objetos dinamicamente. Porém, em alguns cenários, você tem uma estrutura pronto, que pode ser utilizada para criar novas instâncias com estruturas similares. É aí que entram as classes que representam, como se fossem fábricas.
Através do Padrão de Projeto Factory Method, temos uma interface e suas implementações, e a partir de uma classe factory é decidido qual instância utilizar. Sua representação em diagrama de classe da UML nos dá uma ótima visão
A forma mais comum de implementação deste padrão é a utilização de um método factory parametrizado, que criará o objeto adequado de acordo com o parâmetro que o identifica. Neste caso, todos os objetos implementam a interface Product. Isso vai de encontro ao Open-Closed Principle, onde a classe deve estar fechada para modificações, porém aberta para extensões.
Desvantagens do Padrão de Projeto Method Factory
Pelo próprio diagrama você já consegue notar que há a criação de muitas classes. Ou seja, estamos dobrando nossos códigos, onde para cada novo produto, precisamos de um produto concreto e uma fábrica.. Isso implica que quando você tem um problema pequeno, utilizando este Pattern, você pode gerar uma complexidade maior que a esperada.
Vantagens do Padrão Method Factory
A principal vantagem deste padrão é que você ganha muita flexibilidade quando precisa criar novas adições de classes. Ele é bem desacoplado na sua totalidade o que deixa o código flexível e extensível para implementações futuras.
Vejamos um exemplo de uso do Padrão Method Factory em PHP. Vamos criar uma estrutura de uma Fábrica de Instrumentos Musicais. O desafio é o seguinte:
- Precisamos construir Violões (Guitar)
- Temos 2 tipos de instrumentos, os acústicos e os elétricos.
- Nossa fábrica tem que produzir, tantos instrumentos acústicos quando elétricos
- Nossa fábrica tem que permitir a criação de várias marcas(Giannini, Tagima, etc…)
Para resolver esse problema, vamos criar primeiro a estrutura do projeto similar ao descrito no Diagrama de classe UML que representa o pattern Method Factory. Porém, vou implementar uma herança simples só para enriquecer o exemplo prático
Só com este diagrama de classe, já conseguimos ter uma ideia de como é a implementação na prática. Porém, fazer código a código fica mais bacana. Então vamos fazer classe por classe.
Definindo as classes que são responsáveis pela criação (Creators)
Vamos começar pelas factories, porque já vamos tendo uma noção das dependências dos objetos concretos de cada implementação dos Methods Factory.
Interface Musical Instrument Factory
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Factory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\MusicalInstrument; interface MusicalInstrumentFactory { public function createMusicalInstrument(string $brand): MusicalInstrument; }
Electric Guitar Factory
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Factory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\ElectricGuitar; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\MusicalInstrument; class ElectricGuitarFactory implements MusicalInstrumentFactory { public function createMusicalInstrument(string $brand): MusicalInstrument { return new ElectricGuitar($brand); } }
Acoustic Guitar Factory
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Factory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\AcousticGuitar; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\MusicalInstrument; class AcousticGuitarFactory implements MusicalInstrumentFactory { public function createMusicalInstrument(string $brand): MusicalInstrument { return new AcousticGuitar($brand); } }
Definindo as classes que serão responsáveis pelos produtos (Products)
Já tomos as factories prontas , agora vamos completar com as classes que representam os products.
Interface Musical Instrument Product
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Product; interface MusicalInstrumentProduct { public function make(): void; }
Abstract Class Musical Instrument
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Product; abstract class MusicalInstrument implements MusicalInstrumentProduct { private string $brand; public function __construct(string $brand) { $this->brand = $brand; } protected function getBrand(): string { return $this->brand; } }
Acoustic Guitar
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Product; class AcousticGuitar extends MusicalInstrument { public function make(): void { printf("Made a %s acoustic guitar \n\n", $this->getBrand()); } }
Electric Guitar
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod\Product; class ElectricGuitar extends MusicalInstrument { public function make(): void { printf("Made a %s eletric guitar \n\n", $this->getBrand()); } }
Criando um cliente para representar a Fábrica
Aqui forçadamente, temos um “mini problema” de design do código. Veja que no método tem um swicth que testa as condições para gera um violão acústico ou elétrico. Agora, imagine que você quer implementar uma fábrica de Piano. Ele também é um Instrumento musical, mas teríamos que “inevitavelmente” editar esta classe para adicionar mais uma condição. E isso fere um dos princípios SOLID o OCP, Open-Closed Principle ou Principio do aberto/fechado. Ou seja, aberto para extensão e fechado para Modificação. Eu abordo sobre uma soluções para isso 😉 no artigo sobre o Padrão de Projeto Strategy.
Musical Instrument Factory
<?php declare(strict_types=1); namespace Growthdev\DesignPatterns\Creational\FactoryMethod; use Growthdev\DesignPatterns\Creational\FactoryMethod\Factory\AcousticGuitarFactory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Factory\ElectricGuitarFactory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\MusicalInstrument; class MusicalInstrumentFactory { public const ACOUSTIC_GUITAR = 'acoustic_guitar'; public const ELECTRIC_GUITAR = 'electric_guitar'; public function createMusicalInstrument(string $type, string $brand): MusicalInstrument { switch ($type) { case self::ACOUSTIC_GUITAR: return (new AcousticGuitarFactory()) ->createMusicalInstrument($brand); case self::ELECTRIC_GUITAR: return (new ElectricGuitarFactory()) ->createMusicalInstrument($brand); default: throw new \InvalidArgumentException('Invalid musical instrument type'); } } }
Organizei todas as classes em uma estrutura separada segundo suas características. Desta forma fica mais fácil representar esta implementação. Para facilitar, disponibilizei o código completo com os testes no meu Github: https://github.com/growthdev-repo/design-patterns. De toda forma, a organização da estrutura ficou assim:
Criando os testes para para mostrar o Padrão Method Factory em ação
Fiz um teste simples, só para mostrar que nossa Factory está atendendo aos requisitos de me propus a apresentar para você.
Musical Instrument Factory Test
<?php declare(strict_types=1); namespace DesignPatterns\Creational\FactoryMethod; use Growthdev\DesignPatterns\Creational\FactoryMethod\MusicalInstrumentFactory; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\AcousticGuitar; use Growthdev\DesignPatterns\Creational\FactoryMethod\Product\ElectricGuitar; use PHPUnit\Framework\TestCase; final class MusicalInstrumentFactoryTest extends TestCase { public function testCanCreateAcousticGuitar(): void { $factory = new MusicalInstrumentFactory(); $acousticGuitar = $factory->createMusicalInstrument( MusicalInstrumentFactory::ACOUSTIC_GUITAR, 'Giannini Model' ); $acousticGuitar->make(); $this->assertInstanceOf(AcousticGuitar::class, $acousticGuitar); } public function testCanCreateEletricGuitar(): void { $factory = new MusicalInstrumentFactory(); $eletricGuitar = $factory->createMusicalInstrument( MusicalInstrumentFactory::ELECTRIC_GUITAR, 'Tagima Model' ); $eletricGuitar->make(); $this->assertInstanceOf(ElectricGuitar::class, $eletricGuitar); } public function testShouldThrowExceptionWhenUnknownInstrument(): void { $this->expectException(\InvalidArgumentException::class); $factory = new MusicalInstrumentFactory(); $factory->createMusicalInstrument('Unknown', 'Giannini Model'); } }
Como você pode perceber, nossa fábrica de instrumentos musicais está pronta e em perfeito funcionamento.
Acredito que consegui passar bem este conceito. Existem diversos tipos de problemas que conseguimos resolver utilizando o padrão Method Factory. E você? Já utilizou este padrão em algum projeto prático? Deixa aqui um comentário com um case que ajude outras pessoas desenvolvedoras a entender melhor estes conceitos. Vai valer muito apena! Por acreditar!
Se você está chegando aqui agora, publiquei outros artigos explicando sobre outros Padrões de Projetos. Você encontrará os links com um resumo no artigo Resumo dos Padrões de Projetos (Design Patterns). 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.
Seja o primeiro a comentar