Design Pattern

Padrão de Projeto Facade em PHP com exemplo

Capa do artigo Padrão de Projeto Facade em PHP com exemplo
Leitura com foco

Entenda o contexto.
Leve para a prática.

Neste artigo
  1. Qual problema o Facade resolve?
  2. Exemplo do padrão Facade em PHP
  3. Testando o exemplo
  4. Quando usar o padrão Facade?
  5. Cuidados ao usar o Facade
  6. Facade, Adapter e Mediator: qual a diferença?

O Padrão de Projeto Facade (ou Façade) oferece uma interface única e simples para um subsistema complexo. Em vez de o cliente conhecer várias classes e a ordem certa de chamá-las, ele conversa com um objeto só, a fachada, que coordena o trabalho por baixo dos panos.

A fachada não cria regra de negócio nova e não tranca o subsistema: quem precisar de algo específico continua podendo usar as classes internas diretamente. Ela só oferece o caminho mais curto para o uso mais comum.

Diagrama de classe UML do padrão de projeto Facade
Diagrama de classe UML do padrão de projeto Facade

Observe no diagrama os participantes do padrão:

  • Facade (OrderFacade): sabe quais classes do subsistema atendem a cada pedido e em que ordem chamá-las.
  • Classes do subsistema (Inventory, PaymentGateway e Shipping): fazem o trabalho de verdade e não sabem que a fachada existe.
  • Cliente: usa a fachada em vez de falar com cada classe.

Qual problema o Facade resolve?

Imagine o fechamento de um pedido em uma loja virtual. Para concluir a compra você precisa conferir o estoque, reservar os itens, cobrar o cliente e agendar a entrega, nessa ordem. São três classes diferentes e uma sequência que não pode ser trocada.

Agora pense que esse fechamento acontece em três lugares: no controller do site, em um comando de terminal e em um endpoint da API. Sem a fachada, os três conhecem as três classes e repetem a mesma sequência. No dia em que a regra mudar, você altera em três lugares e torce para não esquecer nenhum. Com o Facade, a sequência mora em um lugar só.

Exemplo do padrão Facade em PHP

Vamos para o código. Primeiro, o pedido, que é só um objeto de dados:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Structural\Facade;

final class Order
{
    public readonly string $sku;
    public readonly int $quantity;
    public readonly float $unitPrice;
    public readonly string $customer;
    public readonly string $address;

    public function __construct(string $sku, int $quantity, float $unitPrice, string $customer, string $address)
    {
        $this->sku = $sku;
        $this->quantity = $quantity;
        $this->unitPrice = $unitPrice;
        $this->customer = $customer;
        $this->address = $address;
    }

    public function total(): float
    {
        return $this->quantity * $this->unitPrice;
    }
}

Agora as três classes do subsistema. Repare que nenhuma delas conhece as outras, e nenhuma conhece a fachada.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Structural\Facade;

final class Inventory
{
    /** @var array<string, int> */
    private array $stock;

    /** @param array<string, int> $stock */
    public function __construct(array $stock)
    {
        $this->stock = $stock;
    }

    public function has(string $sku, int $quantity): bool
    {
        return ($this->stock[$sku] ?? 0) >= $quantity;
    }

    public function reserve(string $sku, int $quantity): void
    {
        $this->stock[$sku] -= $quantity;

        printf("Reserved %d x %s\n", $quantity, $sku);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Structural\Facade;

final class PaymentGateway
{
    public function charge(string $customer, float $amount): void
    {
        printf("Charged %.2f to %s\n", $amount, $customer);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Structural\Facade;

final class Shipping
{
    public function schedule(string $sku, int $quantity, string $address): void
    {
        printf("Shipping %d x %s to %s\n", $quantity, $sku, $address);
    }
}

E aqui está a fachada. O método placeOrder() é o único que o cliente precisa conhecer. Dentro dele está a sequência inteira: conferir o estoque, reservar, cobrar e agendar a entrega.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Structural\Facade;

use RuntimeException;

final class OrderFacade
{
    private Inventory $inventory;
    private PaymentGateway $payment;
    private Shipping $shipping;

    public function __construct(Inventory $inventory, PaymentGateway $payment, Shipping $shipping)
    {
        $this->inventory = $inventory;
        $this->payment = $payment;
        $this->shipping = $shipping;
    }

    public function placeOrder(Order $order): void
    {
        if (!$this->inventory->has($order->sku, $order->quantity)) {
            throw new RuntimeException('Out of stock');
        }

        $this->inventory->reserve($order->sku, $order->quantity);
        $this->payment->charge($order->customer, $order->total());
        $this->shipping->schedule($order->sku, $order->quantity, $order->address);
    }
}

O subsistema continua com as mesmas classes e os mesmos métodos. A fachada só juntou, em uma chamada, o que antes eram quatro.

Testando o exemplo

Os testes mostram três situações: o pedido completo, o pedido sem estoque e o uso direto de uma classe do subsistema depois de passar pela fachada.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Structural\Facade;

use Growthdev\DesignPatterns\Structural\Facade\Inventory;
use Growthdev\DesignPatterns\Structural\Facade\Order;
use Growthdev\DesignPatterns\Structural\Facade\OrderFacade;
use Growthdev\DesignPatterns\Structural\Facade\PaymentGateway;
use Growthdev\DesignPatterns\Structural\Facade\Shipping;
use PHPUnit\Framework\TestCase;
use RuntimeException;

final class FacadeTest extends TestCase
{
    public function testExpectFacadeToRunTheWholeCheckout(): void
    {
        $facade = new OrderFacade(new Inventory(['php-book' => 3]), new PaymentGateway(), new Shipping());
        $facade->placeOrder(new Order('php-book', 2, 59.90, 'dev@example.com', '100 Main Street'));

        $this->expectOutputString(
            "Reserved 2 x php-book\n"
            . "Charged 119.80 to dev@example.com\n"
            . "Shipping 2 x php-book to 100 Main Street\n"
        );
    }

    public function testExpectNothingToHappenWhenOutOfStock(): void
    {
        $facade = new OrderFacade(new Inventory(['php-book' => 1]), new PaymentGateway(), new Shipping());

        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Out of stock');
        $this->expectOutputString('');

        $facade->placeOrder(new Order('php-book', 2, 59.90, 'dev@example.com', '100 Main Street'));
    }

    public function testExpectSubsystemToRemainAvailable(): void
    {
        $inventory = new Inventory(['php-book' => 3]);
        $facade = new OrderFacade($inventory, new PaymentGateway(), new Shipping());
        $facade->placeOrder(new Order('php-book', 2, 59.90, 'dev@example.com', '100 Main Street'));

        $this->assertTrue($inventory->has('php-book', 1));
        $this->assertFalse($inventory->has('php-book', 2));
        $this->expectOutputRegex('/^Reserved 2 x php-book/');
    }
}

No primeiro teste, uma única chamada produz as três etapas na ordem certa. No segundo, o estoque não cobre o pedido: a fachada lança a exceção antes de reservar, cobrar ou enviar, e por isso a saída fica vazia. No terceiro, o cliente consulta o Inventory diretamente, o que mostra que a fachada não esconde o subsistema à força.

Quando usar o padrão Facade?

  • Quando um subsistema tem muitas classes e a maioria dos clientes só precisa de um uso comum.
  • Quando você quer isolar o seu código de uma biblioteca de terceiros ou de um sistema legado.
  • Quando quer definir um ponto de entrada para cada camada do sistema e reduzir o acoplamento entre elas.

Um aviso para quem usa Laravel: o recurso que o framework chama de Facade é outra coisa. A documentação do Laravel descreve as facades como “static proxies” para classes do container de serviços, ou seja, um atalho estático para um serviço. O nome é o mesmo, mas o padrão do livro GoF é o que você viu aqui.

Cuidados ao usar o Facade

  • A fachada pode virar uma “classe faz-tudo”. Se ela começar a acumular métodos de assuntos diferentes, divida em mais de uma.
  • Fachada coordena, não decide. Regra de negócio continua nas classes do subsistema.
  • Não use a fachada para proibir o acesso ao subsistema. Se o objetivo é controlar o acesso, o padrão é o Proxy.

Facade, Adapter e Mediator: qual a diferença?

Os três ficam “no meio do caminho” entre objetos, e é por isso que são confundidos. A diferença está na intenção e em quem conhece quem:

PadrãoO que fazQuem conhece quem
FacadeCria uma interface nova e mais simples para um subsistemaA fachada conhece o subsistema; o subsistema não conhece a fachada
AdapterConverte uma interface que já existe em outra que o cliente esperaO adaptador conhece a classe adaptada
MediatorCentraliza a comunicação entre objetos que colaboram entre siOs objetos conhecem o mediador e falam com ele; a conversa vai nos dois sentidos
Diferença entre Facade, Adapter e Mediator.

Esse é mais um padrão estrutural do livro GoF. Para conhecer os outros, veja o artigo Resumo dos Padrões de Projetos.

O código completo deste exemplo, com os testes, está no meu GitHub, na pasta src/Structural/Facade do repositório growthdev-repo/design-patterns.

Veja também: o resumo dos 23 padrões de projeto GoF e os outros padrões estruturais já publicados: Adapter, Bridge, Composite, Decorator, Flyweight, Proxy.

Confiança Sempre!!!

Fontes:

  • [1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.
  • [2] FREEMAN, Eric; ROBSON, Elisabeth; BATES, Bert; SIERRA, Kathy. Head First Design Patterns.
  • [3] Laravel. Facades. https://laravel.com/docs/facades
Sobre quem escreve

Walmir Silva

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.

Ver todos os artigos →

Continue a conversa

Compartilhe sua perspectiva

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Encontre sua próxima resposta

O que você quer aprender?

Preferências de cookies

Os cookies opcionais começam desligados. Você pode mudar a escolha a qualquer momento por esta tela.

NecessáriosSempre ativos

Guardam a sua escolha de cookies e mantêm o site funcionando. Não são usados para publicidade.

Contam visitas e mostram como o site é usado: Google Analytics e Microsoft Clarity, carregados pelo Google Tag Manager. Como esse contêiner também carrega tags de publicidade, as estatísticas só são ativadas junto com Publicidade e marketing.

Exibem anúncios do Google AdSense e medem campanhas e e-mails: pixels da Meta e do TikTok e o Mautic.

Permite que o Google escolha anúncios com base nos seus interesses. Desligado, os anúncios aparecem sem personalização. Depende de Publicidade e marketing.

Também é possível bloquear cookies nas configurações do navegador. Detalhes na Política de privacidade.