← Todos los patrones

Facade

Structural · ver código fuente en GitHub

Leer en: English · Português · Español

Category: Structural

El problema

Lograr que algo se haga requiere coordinar varios subsistemas en un orden específico — llamar a este servicio, luego a aquel, avanzar solo si cada paso tiene éxito. Cada llamador que necesita ese resultado o duplica esa lógica de orquestación, o tiene que aprender los internos de cada subsistema solo para usarlos correctamente. Los propios subsistemas están bien por sí solos; lo que falta es una puerta de entrada más simple para el caso común.

La solución

Agregar una clase que sepa cómo coordinar los subsistemas correctamente, y darle eso a los llamadores en vez de los propios subsistemas. Los subsistemas no cambian y siguen siendo utilizables directamente para llamadores con necesidades más específicas — la fachada es un punto de entrada más simple adicional, no un reemplazo.

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

Ejemplo clásico

classic/HomeTheaterFacade es el ejemplo canónico: watchMovie() enciende el Projector, lo pone en modo panorámico, enciende el Amplifier y ajusta su volumen, luego enciende el DvdPlayer e inicia la película — seis llamadas a través de tres subsistemas, en el único orden que realmente funciona, detrás de un solo método. HomeTheaterFacadeTest verifica la secuencia exacta.

Ejemplo aplicado: orquestación de portabilidad salarial

applied/SalaryPortabilityFacade coordina AccountVerificationService (esta cuenta es siquiera elegible), BacenLookupService (dónde se paga actualmente el salario de este pagador, según el registro del banco central), y NotificationService (avisar al titular de la cuenta que está programado) — cortando en el momento en que cualquier paso falla, de modo que una cuenta no elegible nunca dispara una consulta a BACEN, y un pagador sin banco de nómina registrado nunca dispara una notificación. Ninguno de los tres servicios de subsistema sabe que existen los otros dos; solo la fachada lo sabe. SalaryPortabilityFacadeTest cubre el camino feliz completo y ambos casos de corte.

Cuándo no usarlo

Cobertura de pruebas

100% de cobertura de instrucciones, 100% de cobertura de ramas (JaCoCo). Reprodúzcalo usted mismo:

./gradlew :structural:facade:jacocoTestReport

Informe en structural/facade/build/reports/jacoco/test/html/index.html.

Lecturas adicionales

Pruebas unitarias

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 informe completo de cobertura JaCoCo →