Design Pattern: Factory Method
Tags: writing, programming, productivity, showdev
Published: 2021-10-18 16:15:52

> Como o próprio nome já diz, ele é a nossa fábrica. De que? De objetos uai.

Este é um padrão criacional que fornece um interface de criação de objetos numa superclasse, mas que seja possível alternar o tipo do objeto que retorna dessa criação.

## Explicando o problema

Vamos imaginar que você está desenvolvendo um e-commerce. No primeiro momento sua aplicação apenas aceitará cartão de crédito, portanto sempre que vier um pedido no seu sistema toda a regra de pagamentos e validações ficará dentro da classe `CreditCard`.

```php
<?php
class Cart 
{
    public function checkout() 
    {
        $payment = new CreditCard;
        return $payment?->pay();
    }
}

class CreditCard 
{
    public function pay()
    {
        return 'Transação concluída com sucesso' . PHP_EOL;
    }
}

$cart = new Cart();
echo $cart->checkout(); //Transação concluída com sucesso
```

Agora sua loja está indo muito bem e crescendo, mas seus clientes começam a reclamar e pedem por mais meios de pagamento. Para evitar que o número de vendas caia, logo você começa a desenvolver melhorias adicionando um novo meio de pagamento `Invoice` (Boletos).

É uma boa noticia certo? Mas se você não tomar cuidado no inicio do desenvolvimento, é possível que tudo esteja acoplado a uma única classe, e adicionar qualquer coisa pode alterar toda a base de código. Além disso sempre que for criado um meio de pagamento novo, provavelmente passará por isso tudo novamente.

Vejamos um exemplo adicionando a classe `Invoices` num universo simplificado.

```php
<?php
class Cart 
{
    private $cart;

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

    public function checkout() 
    {

        $payment = null;

        if ($this->cart->payment === 'boleto') {
            $payment = new Invoice;
        } 
        
        if ($this->cart->payment === 'credit') {
            $payment = new CreditCard;
        }

        return $payment?->pay() ?? 'Erro';
    }
}

class Invoice 
{
    public function pay()
    {
        return 'Boleto para ser pago'. PHP_EOL;
    }
}

class CreditCard 
{
    public function pay()
    {
        return 'Transação concluída com sucesso' . PHP_EOL;
    }
}

//Case 1
$myCart = ['payment' => 'boleto'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Boleto para ser pago

//Case 2
$myCart = ['payment' => 'credit'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Transação concluída com sucesso

//Case 3 - Método de pagamento não implementado
$myCart = ['payment' => 'Outro meio de pagamento'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Erro
```

Nesse pequeno exemplo parece até simples (só adicionar uns ifs e já era), mas nem sempre é assim, adicionar uma nova classe num código que depende de classes concretas pode dar uma certa dor de cabeça. 

Pense comigo, como ficaria esse código com 5 meios de pagamentos? Terão 5 ifs? Um Switch? Parece meio ruim né, além de deixar o sódigo bastante sujo, ficará cheio de condicionais que mudam o comportamento da aplicação, podendo até criar um erro inesperado. 

## Solução

O padrão Factory sugere que você não chame diretamente o operador `new`. Você pode estar se perguntando como irá criar os objetos, mas calma, ainda será com o operador `new`, mas ele será chamado dentro desse método chamado de `fábrica`, esses objetos gerados e retornados por esse método serão chamados de `produtos`.

Usando nosso exemplo anterior, vamos aplicar o conceito para entender a ideia dele:

```php
<?php
class Cart 
{
    private $factoryPayment;
    private $cart;

    public function __construct ($cart) 
    {
        $this->factoryPayment = new FactoryPayment;
        $this->cart = $cart;
    }

    public function checkout() 
    {
        $payment = $this->factoryPayment->createPayment($this->cart->payment);
        return $payment?->pay() ?? 'Erro';
    }
}

class FactoryPayment 
{
    public function createPayment($payment) : Payment|null
    {
        return match ($payment) {
            'boleto' => new Invoice,
            'credit' => new CreditCard,
            default => null
        };
    }
}

interface Payment {
    public function pay();
}

class Invoice implements Payment
{
    public function pay()
    {
        return 'Boleto para ser pago'. PHP_EOL;
    }
}

class CreditCard implements Payment
{
    public function pay()
    {
        return 'Transação concluída com sucesso' . PHP_EOL;
    }
}

//Case 1
$myCart = ['payment' => 'boleto'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Boleto para ser pago

//Case 2
$myCart = ['payment' => 'credit'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Transação concluída com sucesso

//Case 3 - Método de pagamento não implementado
$myCart = ['payment' => 'Outro meio de pagamento'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Erro
```

Olhando de início pode parecer sem sentido, pois apenas mudamos a chamada de criação para outra classe. Mas isso ajuda bastante na hora de incluir novos meios de pagamento por exemplo, basta implementar a interface `Payment` com o método `pay` e se tiver tudo correto com a nova classe vai funcionar tudo corretamente sem precisar de alterar nada no código principal, pois está tudo abstraído.

Outro caso muito maneiro é a possibilidade de criar outro factory antes do `FactoryPayment`, para criar gateways de pagamento. Suponha que vc decida pagar boletos (Invoice) com o gateway X e o cartão de crédito com o Y (CreditCard) com esse padrão isso é possível, irei explicar no código cada ponto, veja:

```php
<?php

//Classe principal que será o código cliente que receberá os produtos
class Cart 
{
    private $factoryGateway;
    private $cart;

    public function __construct ($cart) 
    {
        $this->factoryGateway = new FactoryGateway;
        $this->cart = $cart;
    }

    public function checkout() 
    {
        $payment = $this->factoryGateway->createGateway($this->cart->payment);
        return $payment?->pay() ?? 'Erro';
    }
}

// Classe fábrica de gateways que será chamada pelo código cliente
// usando o método de pagamento para decisão.
class FactoryGateway 
{
    public function createGateway($payment) : Payment|null
    {
        $gateway = match ($payment) {
            'boleto' => new GatewayX,
            'credit' => new GatewayY,
            default => null
        };
        return $gateway?->createPayment();
    }
}

// Classe abstrata que será usada como fábrica de pagamentos
abstract class AbstractPayment 
{
    abstract public function createPayment(): Payment;

    protected function authenticate()
    {
        echo 'Autenticado no ' . get_class($this) . PHP_EOL;
    }
}

// Agora temos que implementar os gateways e os métodos que retornarão os produtos
// que no nosso caso são meios de pagamento
class GatewayX extends AbstractPayment
{
    public function createPayment(): Payment
    {
        $this->authenticate();
        return new Invoice;
    }
}

class GatewayY extends AbstractPayment
{
    public function createPayment() : Payment
    {
        $this->authenticate();
        return new CreditCard;
    }
}

// Abaixo temos a interface e os produtos que a implementam. 
interface Payment {
    public function pay();
}

class Invoice implements Payment
{
    public function pay()
    {
        //cria boleto
        return 'Boleto para ser pago'. PHP_EOL;
    }
}

class CreditCard implements Payment
{
    public function pay()
    {
        //valida dados do cartão, limite e etc
        return 'Transação concluída com sucesso' . PHP_EOL;
    }
}

//Case 1
$myCart = ['payment' => 'boleto'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Boleto para ser pago com sucesso no GatewayX

//Case 2
$myCart = ['payment' => 'credit'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Transação concluída com sucesso no GatewayY

//Case 3 - Método de pagamento não implementado
$myCart = ['payment' => 'Outro meio de pagamento'];
$cart = new Cart((object) $myCart);
echo $cart->checkout(); //Erro
```

> OBS: Ele fica bem similar ao próximo padrão que vou mostrar em um artigo futuro, que é o Abstract Factory.
> 

## Pontos de atenção

1. Todos os `produtos` devem implementar a mesma classe ou interface.
    
    Ex: `Payment` é implementado em todos os produtos.
    
2. `Produtos concretos` são implementações diferentes da mesma interface.
    
    Ex: `Invoice` , `CreditCard`.
    
3. Na classe criador, deve ser declarado o método fábrica que retornará os novos objetos produto. Esse retorno deve corresponder à interface do produto.
    
    Ex: Criador `AbstractPayment` possui o método fábrica `createPayment` e é retornado um produto do tipo `Payment`.
    
4. Apesar de ter criador no nome, essa não é a principal responsabilidade dela, é possível ter alguma regra de negócio relacionada aos seus produtos.
    
    Ex: Método `authenticate` da classe criadora `AbstractPayment`.
    
5. Criadores concretos sobrescrevem o método fábrica base para criar um tipo diferente de produto.
    
    Ex: `GatewayX` e `GatewayY`.
    

## SOLID

Aqui temos menção a 2 itens do SOLID:

Princípio de responsabilidade única (S):  Você pode mover o código criador para um único ponto, facilitando a manutenção.

Principio aberto/fechado (O): Você pode adicionar novos produtos na aplicação sem quebrar o código cliente.

## Conclusão

Use esse padrão sempre que você não souber os tipos e dependências dos objetos que seu código irá utilizar, ele separa o código construtor de produtos do código principal. Isso deixa o código de construção do produto mais simples de ser extendido e independente do resto.

Por exemplo, caso queira adicionar mais métodos de pagamento ou gateways, basta implementar as classes correspondentes, adicionar nos métodos fábrica e pronto, seu código está com a nova funcionalidade.

É isso pessoal, chegamos ao fim de mais um artigo, até o próximo.