El patrón Facade proporciona una interfaz unificada y de alto nivel para un conjunto de interfaces en un subsistema. Su objetivo es simplificar el uso de sistemas complejos, ocultando detalles internos y reduciendo la cantidad de dependencias directas que debe conocer el cliente.
En la práctica, una fachada actúa como capa de orquestación: coordina llamadas a servicios, traduce datos y ofrece operaciones de negocio coherentes. Esto facilita el aprendizaje, mejora la mantenibilidad y permite desacoplar módulos.
Los subsistemas grandes suelen exponer numerosas clases, operaciones y detalles de configuración que el cliente debe manejar con exactitud. Esto genera acoplamiento, curva de aprendizaje alta y fragilidad ante cambios. Facade introduce un punto de entrada que encapsula la complejidad y expone una API orientada a tareas.
El patrón resulta especialmente útil al integrar código legado, bibliotecas de terceros o módulos con demasiados detalles internos. Con una fachada se evita que el cliente conozca cada clase interna y se reduce el número de dependencias.
La intención es proporcionar una interfaz única y simplificada para un subsistema. Se motiva cuando:
Una fachada puede ofrecer operaciones de grano grueso que combinan varias llamadas internas, facilitando la reutilización y evitando que los clientes repliquen secuencias de pasos.
Los participantes principales son:
El patrón no prohíbe que el cliente acceda directamente al subsistema, pero la fachada actúa como un camino recomendado. También puede utilizar otras fachadas internas para componer funcionalidades.
Analicemos un ejemplo en Java donde un sistema de pedidos involucra verificación de inventario, procesamiento de pagos y logística. La fachada GestionPedidosFacade
expone una operación de alto nivel realizarPedido
.
public class InventarioService {
public boolean hayStock(String productoId, int cantidad) {
System.out.println("Verificando stock para " + productoId);
return true;
}
}
public class PagoService {
public void procesarPago(String clienteId, double monto) {
System.out.println("Procesando pago de " + monto + " para " + clienteId);
}
}
public class LogisticaService {
public void despachar(String productoId, int cantidad, String destino) {
System.out.println("Despachando " + cantidad + " unidades de " + productoId + " hacia " + destino);
}
}
public class GestionPedidosFacade {
private final InventarioService inventarioService;
private final PagoService pagoService;
private final LogisticaService logisticaService;
public GestionPedidosFacade(InventarioService inventarioService, PagoService pagoService, LogisticaService logisticaService) {
this.inventarioService = inventarioService;
this.pagoService = pagoService;
this.logisticaService = logisticaService;
}
public void realizarPedido(String clienteId, String productoId, int cantidad, String destino, double monto) {
if (!inventarioService.hayStock(productoId, cantidad)) {
throw new IllegalStateException("No hay stock disponible");
}
pagoService.procesarPago(clienteId, monto);
logisticaService.despachar(productoId, cantidad, destino);
System.out.println("Pedido completado para " + clienteId);
}
}
El cliente interactúa con la fachada en lugar de coordinar manualmente los servicios.
Supongamos un módulo que reproduce medios digitales. Incluye decodificadores, servicios de streaming, cacheo y registros. Una fachada permite reproducir un archivo con una sola llamada, ocultando la configuración interna.
public class DecodificadorAudio {
public void decodificar(String archivo) {
System.out.println("Decodificando audio de " + archivo);
}
}
public class DecodificadorVideo {
public void decodificar(String archivo) {
System.out.println("Decodificando video de " + archivo);
}
}
public class CacheMedios {
public boolean estaEnCache(String archivo) {
System.out.println("Verificando cache para " + archivo);
return false;
}
public void almacenar(String archivo) {
System.out.println("Almacenando " + archivo + " en cache");
}
}
public class RegistroEventos {
public void registrar(String mensaje) {
System.out.println("LOG: " + mensaje);
}
}
public class ReproductorFacade {
private final DecodificadorAudio decodificadorAudio = new DecodificadorAudio();
private final DecodificadorVideo decodificadorVideo = new DecodificadorVideo();
private final CacheMedios cache = new CacheMedios();
private final RegistroEventos registroEventos = new RegistroEventos();
public void reproducir(String archivo) {
registroEventos.registrar("Reproducción solicitada para " + archivo);
if (!cache.estaEnCache(archivo)) {
cache.almacenar(archivo);
decodificadorAudio.decodificar(archivo);
decodificadorVideo.decodificar(archivo);
}
System.out.println("Reproduciendo " + archivo);
}
}
El cliente solo necesita conocer la fachada:
public class AplicacionMultimedia {
public static void main(String[] args) {
ReproductorFacade reproductor = new ReproductorFacade();
reproductor.reproducir("pelicula.mp4");
}
}
Es recomendable colocar las fachadas cerca de la capa de aplicación o infraestructura, documentar su responsabilidad y mantenerlas delgadas para evitar lógica excesiva.
Una fachada puede volverse un monolito si concentra demasiadas responsabilidades. Se sugiere dividirlas por contexto o caso de uso. Además, si el cliente necesita capacidades avanzadas, la fachada no debe impedir el acceso directo a componentes especializados.
Otro riesgo es que la fachada termine duplicando reglas de dominio o mezclando niveles de abstracción. Mantener una separación clara entre orquestación (fachada) y reglas de negocio ayuda a preservar la coherencia.
Este patrón es conveniente cuando:
Facade se complementa con otros patrones como Adapter (para traducir interfaces específicas dentro de la fachada) y Mediator (cuando la interacción entre componentes requiere reglas coordinadas).