Los patrones vistos hasta ahora afinan relaciones entre pocas piezas: dos interfaces que no encajan, dos jerarquías, un árbol, una cebolla de capas. Facade opera a otra escala: cuando un subsistema entero —media docena de servicios con su orden, sus reglas y sus errores— debe ser usado por muchos clientes, alguien tiene que saber orquestarlo. La pregunta de diseño es: ¿ese conocimiento vive repetido en cada cliente, o vive una sola vez detrás de una puerta simple? En PideYa la respuesta la está pidiendo a gritos el checkout: confirmar un pedido exige hoy que cada aplicación cliente coordine cinco subsistemas a mano.
Contenido
- El problema en PideYa: el checkout esparcido
- Intención y estructura del patrón
- Implementación Java:
FachadaCheckout - Qué NO es una fachada
- Fachadas por capas y en APIs públicas
- Cuándo usarlo y cuándo no
- Errores comunes, ejercicios y conclusión
El problema en PideYa: el checkout esparcido
Confirmar un pedido en PideYa involucra, en orden, con manejo de errores en cada paso:
- Validar el carrito: platos disponibles (la carta Composite sabe responder), importe mínimo del restaurante, dirección dentro de la zona de reparto.
- Calcular impuestos: con la
CalculadoraImpuestosdel mercado correcto — la familia por país que blindamos con Abstract Factory. - Cobrar: con la
PasarelaPagodel mercado (Redsys, Conekta o elAdaptadorPayPal); si el pago falla, no debe quedar rastro del pedido. - Crear y persistir el pedido: montándolo con el
Pedido.Buildery guardándolo en el repositorio. - Notificar: al cliente (confirmación) y al restaurante (nueva comanda), vía
ServicioNotificaciones.
Ese conocimiento —qué llamar, en qué orden, qué hacer si el paso 3 falla después del 2— está hoy copiado en cada cliente: la app iOS, la app Android, la web, el puesto de atención telefónica. Las consecuencias son las esperables:
- Duplicación con divergencia: la web valida el importe mínimo antes de los impuestos; Android, después. Un arreglo en el flujo hay que repetirlo por cuadruplicado, y alguna copia siempre queda atrás.
- Acoplamiento masivo: cuatro clientes conocen cinco subsistemas cada uno (20 dependencias); cambiar la firma de un servicio interno sacude todas las aplicaciones.
- Conocimiento en la capa equivocada: la regla "si el cobro falla, liberar el carrito y no notificar" es negocio puro, y vive en código de interfaz de usuario.
El primer instinto podría ser "simplifiquemos los servicios". Pero los servicios están bien: cada uno hace una cosa (SRP) y tiene su complejidad legítima. Lo que sobra no es complejidad de las piezas: es conocimiento de la coordinación esparcido por los clientes.
Intención y estructura del patrón
Intención (GoF): proporcionar una interfaz unificada a un conjunto de interfaces de un subsistema. Facade define una interfaz de más alto nivel que hace el subsistema más fácil de usar.
La solución es casi anticlimática de puro simple: una clase que ofrece las operaciones de alto nivel que los clientes de verdad necesitan ("confirmar este carrito con este método de pago") y que por dentro conoce y coordina el subsistema. Toda la sofisticación del patrón está en decidir bien esa interfaz de alto nivel, no en su mecánica.
classDiagram
class FachadaCheckout {
+confirmarPedido(carrito: Carrito, datosPago: DatosPago) ConfirmacionPedido
}
class ValidadorCarrito { +validar(carrito) }
class CalculadoraImpuestos { <<interface>> +calcular(base) BigDecimal }
class PasarelaPago { <<interface>> +cobrar(importe, datos) ResultadoPago }
class RepositorioPedidos { +guardar(pedido) }
class ServicioNotificaciones { +notificarConfirmacion(pedido) }
class AppMovil
class WebPideYa
class AtencionTelefonica
AppMovil --> FachadaCheckout
WebPideYa --> FachadaCheckout
AtencionTelefonica --> FachadaCheckout
FachadaCheckout --> ValidadorCarrito
FachadaCheckout --> CalculadoraImpuestos
FachadaCheckout --> PasarelaPago
FachadaCheckout --> RepositorioPedidos
FachadaCheckout --> ServicioNotificaciones
| Rol GoF | En PideYa |
|---|---|
| Facade | FachadaCheckout |
| Subsystem classes (no conocen la fachada) | ValidadorCarrito, CalculadoraImpuestos, PasarelaPago, RepositorioPedidos, ServicioNotificaciones |
| Client | Apps móviles, web, atención telefónica |
La geometría cuenta la historia: antes, un grafo de 4 clientes × 5 servicios; después, un embudo. Los clientes dependen de una clase; el subsistema no sabe que la fachada existe (flechas solo de ida). El recuento de dependencias cae de 20 a 4 + 5, y sobre todo, el flujo de negocio pasa a estar escrito una vez.
Implementación Java: FachadaCheckout
En el módulo 2 apareció un embrión de esta idea: aquel ServicioCheckout que cobraba con la familia del mercado. La fachada lo completa con el flujo entero, y recibe sus piezas inyectadas —las dependientes del mercado, recién salidas de la FabricaMercado—:
public class FachadaCheckout {
private final ValidadorCarrito validador;
private final CalculadoraImpuestos impuestos;
private final PasarelaPago pasarela;
private final RepositorioPedidos repositorio;
private final ServicioNotificaciones notificaciones;
public FachadaCheckout(ValidadorCarrito validador,
FabricaMercado fabricaMercado,
RepositorioPedidos repositorio,
ServicioNotificaciones notificaciones) {
this.validador = validador;
this.impuestos = fabricaMercado.crearCalculadoraImpuestos(); // familia coherente
this.pasarela = fabricaMercado.crearPasarelaPago(); // del módulo 2
this.repositorio = repositorio;
this.notificaciones = notificaciones;
}
/** LA operación de alto nivel: todo el flujo de confirmación, una sola llamada. */
public ConfirmacionPedido confirmarPedido(Carrito carrito, DatosPago datosPago) {
// 1. Validar: disponibilidad, importe mínimo, zona de reparto.
validador.validar(carrito); // lanza CarritoInvalidoException
// 2. Impuestos del mercado correcto.
BigDecimal base = carrito.getTotalSinImpuestos();
BigDecimal impuesto = impuestos.calcular(base);
BigDecimal total = base.add(impuesto);
// 3. Cobrar. Si falla, aquí termina todo: aún no hay pedido que deshacer.
ResultadoPago resultado = pasarela.cobrar(total, datosPago);
if (!resultado.esAceptado()) {
throw new PagoRechazadoException(resultado.getMotivo());
}
// 4. Crear el pedido (Builder del módulo 2) y persistirlo.
Pedido pedido = Pedido.builder(carrito.getCliente(), carrito.getRestaurante())
.lineas(carrito.getLineas())
.importeTotal(total)
.referenciaPago(resultado.getIdTransaccion())
.build();
repositorio.guardar(pedido);
// 5. Notificar a cliente y restaurante.
notificaciones.notificarConfirmacion(pedido);
return new ConfirmacionPedido(pedido.getNumero(), total, pedido.getHoraEstimada());
}
}Y los cuatro clientes quedan reducidos a lo suyo — interfaz de usuario:
// En la app móvil, la web o donde sea:
ConfirmacionPedido conf = fachadaCheckout.confirmarPedido(carrito, datosPago);
mostrarPantallaExito(conf);Detalles que hacen buena a esta fachada:
- El orden con intención: cobrar antes de persistir hace innecesario el "deshacer pedido" si el pago falla. La decisión más delicada del flujo está tomada una vez, en el sitio correcto, y comentada.
- Habla el idioma del cliente: recibe
CarritoyDatosPago, devuelve unaConfirmacionPedidocompacta (número, total, hora estimada) — no exponeResultadoPago, ni elPedidoentero, ni tipos internos del subsistema. Una fachada que filtra tipos internos hacia fuera es un túnel, no una puerta. - Compone lo ya construido: la coherencia por mercado la sigue garantizando la
FabricaMercado; el montaje del pedido, su Builder; el envío, el servicio de notificaciones (con sus decoradores de reintentos, invisibles desde aquí). La fachada no reimplementa nada: coordina.
Qué NO es una fachada
El patrón se malinterpreta a menudo por exceso. Tres deslindes importantes:
No prohíbe el acceso directo al subsistema. El GoF es explícito: los clientes que necesiten la potencia completa pueden seguir usando las clases del subsistema. El panel de administración de PideYa consulta RepositorioPedidos directamente para sus listados, y es correcto: la fachada ofrece un atajo para los casos comunes, no un peaje obligatorio. (Otra cosa es que un equipo decida además hacerla punto de entrada único de un módulo — decisión de arquitectura legítima, pero adicional al patrón.)
No añade estado ni lógica de negocio propia. La fachada coordina; las reglas viven en el subsistema. Si FachadaCheckout empezara a calcular descuentos o a guardar contadores de pedidos, estaría dejando de ser fachada para convertirse en un servicio más — con el agravante de que su posición central la convierte en imán de responsabilidades: el camino directo al God Object que catalogaremos entre los antipatrones.
No es "cualquier clase que llama a otras". La marca del patrón es la asimetría de complejidad: interfaz de alto nivel simple delante, subsistema complejo detrás, y clientes que gracias a ella no conocen el subsistema. Si la "fachada" tiene un método por cada método interno, es un pasamanos que añade indirección sin restar complejidad.
Fachadas por capas y en APIs públicas
La idea escala más allá de una clase suelta:
- Fachadas por capas: en arquitecturas en capas, cada subsistema grande expone su fachada y esconde sus tripas:
FachadaCocina(recepción y estados de comandas),FachadaReparto(asignación de repartidores, seguimiento),FachadaCheckout. Las capas superiores hablan solo con fachadas; los equipos pueden reorganizar el interior de su subsistema sin romper a nadie. Es el patrón haciendo de frontera de módulo — embrión de lo que, a otra escala, harán los servicios en las arquitecturas modernas (solo mención). - Fachadas en APIs públicas: cuando PideYa publique su API para partners ("crear pedido", "consultar estado"), esa API será una fachada institucional: operaciones de alto nivel, tipos propios de la frontera, y ni rastro de los veinte servicios internos. Las librerías lo hacen igual:
SLF4J("Simple Logging Facade for Java") es una fachada confesa sobre los subsistemas de logging;javax.xml.parserslo es sobre la maquinaria de parseo XML. - Fachada + Adapter, el dúo de frontera: de puertas afuera, una fachada simplifica lo nuestro para otros; un adaptador traduce lo ajeno para nosotros. En la frontera de todo sistema sano suelen encontrarse ambos, cada uno mirando a un lado.
Cuándo usarlo y cuándo no
Úsalo cuando:
- Varios clientes repiten la misma coordinación de un subsistema (el síntoma del checkout: flujo copiado con divergencias).
- Quieres estratificar: definir puntos de entrada por capa o módulo y reducir el acoplamiento entre subsistemas.
- Un flujo de negocio delicado (orden, transaccionalidad, errores) merece vivir escrito una sola vez y con tests propios.
- Publicas una API —interna entre equipos o externa a partners— y necesitas una superficie estable que desacople a los consumidores de tu evolución interna.
No lo uses cuando:
- Hay un solo cliente y una sola llamada simple: envolver dos llamadas en una clase nueva es indirección sin renta (KISS).
- La usarías para esconder un mal diseño: si el subsistema es un nudo incomprensible, la fachada solo pone una cortina delante; el nudo sigue creciendo detrás. Primero ordena (quizá con los otros patrones del módulo), luego, si aún aporta, fachada.
- Cada cliente necesita un flujo distinto: una fachada con quince variantes de
confirmarPedido(...)sobrecargadas es la duplicación original, ahora centralizada y con parámetros booleanos.
Relación con otros patrones (solo mención): Adapter traduce una interfaz, Facade simplifica muchas — el careo fino, en la comparativa; la fachada suele crearse con las fábricas del módulo 2 y ser instancia única compartida (Singleton o, mejor, inyección); Mediator también centraliza interacciones, pero entre colegas que se comunican entre sí y con tráfico de ida y vuelta — lo distinguiremos en el módulo 4; y dentro del subsistema, la fachada convive con todo lo ya visto sin fricción.
Errores Comunes y Consejos
- La fachada que engorda. Cada sprint alguien añade "un metodito más" y a los dos años
FachadaCheckouttiene 40 métodos y lógica propia. Vigila la báscula: si crecen los casos de uso, crea fachadas por área (checkout, seguimiento, valoraciones), no una fachada universal. - Filtrar tipos internos. Devolver
ResultadoPagoo entidades JPA del subsistema a través de la fachada re-acopla a los clientes con lo que queríamos esconder. La frontera define sus propios tipos (ConfirmacionPedido), igual que exigimos a los adaptadores. - Fachada obligatoria por dogma. Prohibir todo acceso directo al subsistema "porque hay fachada" obliga a duplicar en ella operaciones legítimas de bajo nivel. Recuerda: el patrón simplifica el caso común; no jura exclusividad.
- Lógica de negocio de contrabando. El
if (resultado.esAceptado())es coordinación (decidir el flujo); unif (cliente.esVip()) descuento = ...sería negocio (decidir reglas) y pertenece al subsistema. La línea es fina: pregúntate si la regla tendría sentido también invocada desde otro flujo — si sí, abajo con ella. - No testear la fachada como flujo. La fachada es el lugar natural de los tests de orquestación: con dobles de los cinco servicios, verifica el orden, el corte cuando el pago falla, la no-notificación en error. Si esos tests no existen, el flujo más delicado del negocio está sin red.
- Consejo: diseña la interfaz de la fachada desde el cliente, no desde el subsistema: primero escribe cómo quieres que se lea el código de la app (
confirmarPedido(carrito, pago)), y luego haz que la fachada lo cumpla. Las fachadas diseñadas desde dentro acaban oliendo a las tripas que debían ocultar.
Ejercicios
Ejercicio 1: la fachada de seguimiento
La pantalla "¿dónde está mi pedido?" necesita: estado de la comanda (ServicioCocina.estadoDe(numeroPedido)), posición y nombre del repartidor si ya salió (ServicioReparto.repartoDe(numeroPedido)), y hora estimada recalculada (CalculadoraETA.estima(comanda, reparto)). Hoy las tres llamadas y su combinación están copiadas en iOS, Android y web. Diseña FachadaSeguimiento: firma pública, tipo de retorno propio de la frontera y esqueleto de implementación.
Ejercicio 2: la fachada impostora
¿Qué dos violaciones del patrón contiene esta versión y por qué son peligrosas?
public class FachadaCheckout {
private int pedidosConfirmadosHoy = 0; // (a)
public ConfirmacionPedido confirmarPedido(Carrito carrito, DatosPago datos) {
if (carrito.getCliente().esVip() && pedidosConfirmadosHoy < 100) {
carrito.aplicarDescuento(new BigDecimal("0.05")); // (b)
}
// ... resto del flujo como antes ...
pedidosConfirmadosHoy++;
// ...
}
}Ejercicio 3: ¿acceso directo o ampliar la fachada?
El equipo de contabilidad necesita, para su cierre diario, la lista de transacciones de pago del día tal cual las devuelve la pasarela. Discute: ¿deben pedirla a través de FachadaCheckout o ir directos al subsistema de pagos? Da un criterio general reutilizable.
Soluciones
Solución 1:
public class FachadaSeguimiento {
private final ServicioCocina cocina;
private final ServicioReparto reparto;
private final CalculadoraETA eta;
public FachadaSeguimiento(ServicioCocina cocina, ServicioReparto reparto, CalculadoraETA eta) {
this.cocina = cocina; this.reparto = reparto; this.eta = eta;
}
/** Tipo propio de la frontera: solo lo que la pantalla necesita. */
public record EstadoPedidoCliente(String fase, String nombreRepartidor,
Posicion posicionRepartidor, LocalTime horaEstimada) {}
public EstadoPedidoCliente estadoDe(NumeroPedido numero) {
EstadoComanda comanda = cocina.estadoDe(numero);
Optional<Reparto> enCurso = reparto.repartoDe(numero);
LocalTime estimada = eta.estima(comanda, enCurso.orElse(null));
return new EstadoPedidoCliente(
comanda.faseLegible(),
enCurso.map(Reparto::getNombreRepartidor).orElse(null),
enCurso.map(Reparto::getPosicion).orElse(null),
estimada);
}
}Claves: una sola llamada para los tres clientes, retorno propio (EstadoPedidoCliente, un record de frontera) en vez de filtrar EstadoComanda/Reparto, y la combinación (qué pasa si aún no hay repartidor) escrita una vez.
Solución 2: (a) Estado propio: pedidosConfirmadosHoy convierte la fachada en dueña de un dato de negocio que nadie más ve — se pierde al reiniciar, falla con varias instancias y duplica una verdad que pertenece al repositorio. (b) Lógica de negocio de contrabando: la regla del descuento VIP vive donde nadie la buscará (¿la encontrará el equipo de promociones cuando cambie la política?) y es invisible para cualquier otro flujo que confirme pedidos (la API de partners no la aplicaría: precios inconsistentes). Ambas cosas además hacen a la fachada difícil de sustituir o testear, que era su gracia: debe volver a ser coordinación pura y las reglas, bajar al subsistema (servicio de promociones, repositorio).
Solución 3: directos al subsistema de pagos (o mejor, a una fachada del área de pagos si existe). Criterio reutilizable: la fachada sirve los casos de uso comunes de sus clientes, en su nivel de abstracción; contabilidad pide datos crudos de un subsistema concreto, en el vocabulario de ese subsistema, para un caso de uso ajeno al checkout. Ampliar FachadaCheckout con transaccionesDelDia() la desviaría de su propósito y la engordaría (error "fachada que engorda"). El patrón no prohíbe el acceso directo: úsalo cuando el caso de uso no es de la fachada.
Conclusión
Facade pone una puerta simple delante de un subsistema complejo: una clase que ofrece las operaciones de alto nivel que los clientes realmente necesitan, escribe el flujo delicado una sola vez y reduce el acoplamiento de un grafo a un embudo. Sabes también lo que no es: ni prohibición de acceso directo, ni almacén de estado, ni percha para lógica de negocio. En PideYa, FachadaCheckout reunió validación, impuestos por mercado, cobro, construcción del pedido y notificación — todo reutilizando piezas de este módulo y del anterior, que es la mejor señal de que el sistema está bien compuesto.
Cambiamos ahora de preocupación por completo: del orden a la escala. El mapa en tiempo real de PideYa dibuja miles de repartidores y restaurantes, y cada marcador arrastra su icono, sus bytes, su memoria... multiplicados por diez mil. Cuando los objetos son multitud, la pregunta estructural es otra: ¿cuánto de lo que cada objeto carga es realmente suyo, y cuánto podría compartirse entre todos? Nos vemos en Flyweight.
Curso de Patrones de Diseño de Software
Módulo 1: Introducción a los Patrones de Diseño
- ¿Qué son los Patrones de Diseño?
- Historia y Origen de los Patrones de Diseño
- Principios de Diseño: SOLID y Otros Fundamentos
- UML Esencial para Entender Patrones
- Clasificación de los Patrones de Diseño
- Ventajas y Desventajas de Usar Patrones de Diseño
Módulo 2: Patrones Creacionales
- Introducción a los Patrones Creacionales
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa y Elección de Patrones Creacionales
Módulo 3: Patrones Estructurales
- Introducción a los Patrones Estructurales
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa y Elección de Patrones Estructurales
Módulo 4: Patrones de Comportamiento
- Introducción a los Patrones de Comportamiento
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa y Elección de Patrones de Comportamiento
Módulo 5: Aplicación de Patrones de Diseño
- Cómo Seleccionar el Patrón Adecuado
- Ejemplos Prácticos de Uso de Patrones
- Patrones de Diseño en Proyectos Reales
- Refactorización Usando Patrones de Diseño
- Antipatrones: Cuándo los Patrones se Vuelven un Problema
Módulo 6: Patrones de Diseño Avanzados
- Patrones de Diseño en Arquitecturas Modernas
- Patrones de Diseño en Microservicios
- Patrones de Diseño en Sistemas Distribuidos
- Patrones de Concurrencia
- Patrones de Diseño en Desarrollo Ágil
