← Todos os padrões

Facade

Structural · ver código-fonte no GitHub

Leia em: English · Português · Español

Category: Structural

O problema

Fazer algo acontecer exige coordenar vários subsistemas numa ordem específica — chamar esse serviço, depois aquele, só prosseguir se cada passo tiver sucesso. Todo chamador que precisa desse resultado ou duplica essa lógica de orquestração, ou tem que aprender os detalhes internos de todo subsistema só pra usá-los corretamente. Os próprios subsistemas estão bem sozinhos; o que falta é uma porta de entrada mais simples pro caso comum.

A solução

Adicionar uma classe que sabe como coordenar os subsistemas corretamente, e dar aos chamadores ela em vez dos próprios subsistemas. Os subsistemas não mudam e continuam utilizáveis diretamente por chamadores com necessidades mais específicas — a fachada é um ponto de entrada mais simples adicional, não uma substituição.

classDiagram
    class Facade {
        +operation()
    }
    class SubsystemA
    class SubsystemB
    class SubsystemC
    Facade --> SubsystemA
    Facade --> SubsystemB
    Facade --> SubsystemC
    Client --> Facade

Exemplo clássico

classic/HomeTheaterFacade é o exemplo canônico: watchMovie() liga o Projector, coloca em modo widescreen, liga o Amplifier e ajusta seu volume, então liga o DvdPlayer e inicia o filme — seis chamadas através de três subsistemas, na única ordem que de fato funciona, atrás de um único método. HomeTheaterFacadeTest verifica a sequência exata.

Exemplo aplicado: orquestração de portabilidade de salário

applied/SalaryPortabilityFacade coordena AccountVerificationService (essa conta é sequer elegível), BacenLookupService (onde o salário desse pagador é pago atualmente, segundo o registro do banco central), e NotificationService (avisar o titular da conta que está agendado) — fazendo short-circuit no momento em que qualquer passo falha, de modo que uma conta inelegível nunca dispara uma consulta ao BACEN, e um pagador sem banco de folha registrado nunca dispara uma notificação. Nenhum dos três serviços de subsistema sabe que os outros dois existem; só a fachada sabe. SalaryPortabilityFacadeTest cobre o caminho feliz completo e os dois casos de short-circuit.

Quando não usar

Cobertura de testes

100% de cobertura de instrução, 100% de cobertura de branch (JaCoCo). Reproduza você mesmo:

./gradlew :structural:facade:jacocoTestReport

Relatório em structural/facade/build/reports/jacoco/test/html/index.html.

Leitura complementar

Testes unitários

src/test/java/com/designpatterns/structural/facade/classic/HomeTheaterFacadeTest.java
package com.designpatterns.structural.facade.classic;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class HomeTheaterFacadeTest {

    @Test
    void watchMovieOrchestratesEverySubsystemInTheRightOrder() {
        HomeTheaterFacade homeTheater = new HomeTheaterFacade(new Amplifier(), new DvdPlayer(), new Projector());

        var log = homeTheater.watchMovie("The Matrix");

        assertThat(log).containsExactly(
                "Projector on",
                "Projector in widescreen mode",
                "Amplifier on",
                "Amplifier volume set to 5",
                "DVD player on",
                "Playing \"The Matrix\""
        );
    }
}
src/test/java/com/designpatterns/structural/facade/applied/SalaryPortabilityFacadeTest.java
package com.designpatterns.structural.facade.applied;

import org.junit.jupiter.api.Test;

import java.util.Map;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

class SalaryPortabilityFacadeTest {

    private final AccountVerificationService verification = new AccountVerificationService(Set.of("acc-1"));
    private final BacenLookupService bacenLookup = new BacenLookupService(Map.of("111.111.111-11", "Bank A"));
    private final SalaryPortabilityFacade facade =
            new SalaryPortabilityFacade(verification, bacenLookup, new NotificationService());

    @Test
    void schedulesPortabilityWhenEverySubsystemAgrees() {
        PortabilityResult result = facade.requestPortability("acc-1", "111.111.111-11");

        assertThat(result.scheduled()).isTrue();
        assertThat(result.fromBank()).isEqualTo("Bank A");
        assertThat(result.message()).isEqualTo("Notice sent to account acc-1: portability from Bank A scheduled");
    }

    @Test
    void rejectsAnIneligibleAccountWithoutEverCallingBacen() {
        PortabilityResult result = facade.requestPortability("acc-unknown", "111.111.111-11");

        assertThat(result.scheduled()).isFalse();
        assertThat(result.message()).isEqualTo("account not eligible for portability");
    }

    @Test
    void rejectsWhenBacenHasNoPayrollRegistrationForTheTaxId() {
        PortabilityResult result = facade.requestPortability("acc-1", "999.999.999-99");

        assertThat(result.scheduled()).isFalse();
        assertThat(result.message()).isEqualTo("no payroll registration found at BACEN");
    }
}

Ver relatório completo de cobertura JaCoCo →