Patrón Facade: Simplificando Sistemas Complejos

¿Qué es el Patrón Facade?
El patrón Facade proporciona una interfaz simplificada a un conjunto complejo de clases, biblioteca o framework. Oculta la complejidad de los subsistemas tras una única clase que expone solo las operaciones necesarias para el cliente.
classDiagram
class Facade {
+operation()
}
class SubsystemA {
+operationA()
}
class SubsystemB {
+operationB()
}
class SubsystemC {
+operationC()
}
class Client
Client --> Facade
Facade --> SubsystemA
Facade --> SubsystemB
Facade --> SubsystemCCuándo Usar Facade
- Simplificar el acceso a un sistema complejo
- Desacoplar el código cliente de los subsistemas
- Proporcionar un punto de entrada unificado
- Organizar subsistemas en capas
El Problema
En un sistema e-commerce, tenemos múltiples servicios independientes que el cliente debe coordinar:
// Sin Facade - El cliente debe coordinar todo manualmente
const inventory = new InventoryService();
const payment = new PaymentService();
const shipping = new ShippingService();
const notification = new NotificationService();
// Verificar stock
for (const item of items) {
const inStock = await inventory.checkStock(item.productId, item.quantity);
if (!inStock) throw new Error('Sin stock');
reservations.push(await inventory.reserveStock(item.productId, item.quantity));
}
// Calcular envío
const shippingOptions = await shipping.calculateShipping(address, items);
const selectedShipping = shippingOptions[0];
// Procesar pago
const authId = await payment.authorize(total, 'EUR', paymentMethod);
await payment.capture(authId);
// ... más pasos complejos ...La Solución: Facade
1. Identificar los Subsistemas
class InventoryService {
async checkStock(productId: string, quantity: number): Promise<boolean> { }
async reserveStock(productId: string, quantity: number): Promise<string> { }
async confirmReservation(reservationId: string): Promise<void> { }
async releaseReservation(reservationId: string): Promise<void> { }
}
class PaymentService {
async authorize(amount: number, currency: string, method: string): Promise<string> { }
async capture(authorizationId: string): Promise<string> { }
async voidAuthorization(authorizationId: string): Promise<void> { }
}
class ShippingService {
async calculateShipping(address: Address, items: OrderItem[]): Promise<ShippingOption[]> { }
async createShipment(orderId: string, address: Address, carrier: string): Promise<string> { }
}
class NotificationService {
async sendOrderConfirmation(email: string, orderId: string): Promise<void> { }
async sendPaymentReceipt(email: string, amount: number): Promise<void> { }
}2. Definir la Interfaz Simplificada
El cliente solo necesita una operación:
interface OrderRequest {
userId: string;
email: string;
items: Array<{ productId: string; quantity: number; price: number }>;
shippingAddress: Address;
paymentMethod: string;
}
interface OrderResult {
orderId: string;
total: number;
shippingCost: number;
estimatedDelivery: Date;
trackingId?: string;
}3. Implementar el Facade
El Facade coordina todos los subsistemas:
class OrderFacade {
private inventory = new InventoryService();
private payment = new PaymentService();
private shipping = new ShippingService();
private notification = new NotificationService();
async placeOrder(request: OrderRequest): Promise<OrderResult> {
const reservations: string[] = [];
let authorizationId: string | null = null;
try {
// 1. Verificar stock y reservar
for (const item of request.items) {
const inStock = await this.inventory.checkStock(item.productId, item.quantity);
if (!inStock) {
throw new Error(`Producto ${item.productId} sin stock`);
}
const reservationId = await this.inventory.reserveStock(item.productId, item.quantity);
reservations.push(reservationId);
}
// 2. Calcular totales
const subtotal = request.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const shippingOptions = await this.shipping.calculateShipping(
request.shippingAddress,
request.items
);
const shippingCost = shippingOptions[0].cost;
const total = subtotal + shippingCost;
// 3. Procesar pago
authorizationId = await this.payment.authorize(total, 'EUR', request.paymentMethod);
await this.payment.capture(authorizationId);
// 4. Confirmar reservas
for (const reservationId of reservations) {
await this.inventory.confirmReservation(reservationId);
}
// 5. Crear envío
const orderId = `ORD-${Date.now()}`;
const trackingId = await this.shipping.createShipment(
orderId,
request.shippingAddress,
shippingOptions[0].carrier
);
// 6. Enviar notificaciones
await this.notification.sendOrderConfirmation(request.email, orderId);
await this.notification.sendPaymentReceipt(request.email, total);
return {
orderId,
total,
shippingCost,
estimatedDelivery: shippingOptions[0].estimatedDelivery,
trackingId,
};
} catch (error) {
// Rollback en caso de error
for (const reservationId of reservations) {
await this.inventory.releaseReservation(reservationId);
}
if (authorizationId) {
await this.payment.voidAuthorization(authorizationId);
}
throw error;
}
}
}4. Usar el Facade
// Con Facade - Una sola llamada
const facade = new OrderFacade();
const result = await facade.placeOrder({
userId: 'user_123',
email: '[email protected]',
items: [
{ productId: 'PROD_001', quantity: 2, price: 29.99 },
],
shippingAddress: {
street: 'Calle Principal 123',
city: 'Madrid',
country: 'España',
postalCode: '28001',
},
paymentMethod: 'card_visa_****1234',
});
console.log(`Pedido ${result.orderId} completado!`);Beneficios del Patrón Facade
| Beneficio | Descripción |
|---|---|
| Simplicidad | El cliente solo conoce una interfaz |
| Desacoplamiento | Cambios en subsistemas no afectan al cliente |
| Mantenibilidad | Lógica de coordinación centralizada |
| Testabilidad | Fácil de mockear para tests |
Resumen
| Aspecto | Descripción |
|---|---|
| Propósito | Simplificar acceso a sistemas complejos |
| Componentes | Facade, Subsystems, Client |
| Ventaja | Desacoplamiento, punto de entrada único |
| Desventaja | Puede convertirse en "god object" si crece demasiado |
Conclusión
El patrón Facade es esencial para simplificar sistemas complejos y crear APIs limpias. Es ampliamente usado en librerías y frameworks para ocultar la complejidad interna.
En el próximo artículo exploraremos el Patrón State, que permite cambiar el comportamiento de un objeto según su estado interno.
Basado en "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).