Patrones de diseño avanzados en JavaScript: de la teoría a la práctica
\n\nLos patrones de diseño son soluciones probadas para problemas típicos en el desarrollo de software. Para un desarrollador de JavaScript que trabaja con Node.js, comprender los patrones avanzados abre el camino para crear sistemas escalables, flexibles y fáciles de mantener. En este artículo, analizaremos cuatro patrones clave: Observer, Proxy, Chain of Responsibility y Strategy. Cada uno será ilustrado con un ejemplo práctico en Node.js.
\n\n1. Patrón Observer (Observador): arquitectura basada en eventos en Node.js
\n\nObserver es uno de los patrones de comportamiento fundamentales. Define una dependencia de "uno a muchos" entre objetos: cuando un objeto (sujeto) cambia su estado, todos los objetos dependientes (observadores) son notificados y actualizados automáticamente. En Node.js, este patrón es la base del módulo EventEmitter.
Ejemplo: sistema de notificaciones para una tienda en línea
\nImaginemos que tenemos un servidor que gestiona pedidos. Al cambiar el estado de un pedido, es necesario notificar a varios subsistemas: registro (logging), envío de correo electrónico y actualización de análisis.
\n\nconst EventEmitter = require('events');\n\n// Sujeto (Subject)\nclass OrderManager extends EventEmitter {\n constructor() {\n super();\n this.orders = [];\n }\n\n createOrder(orderId, items) {\n const order = { orderId, items, status: 'created', createdAt: new Date() };\n this.orders.push(order);\n this.emit('order:created', order);\n return order;\n }\n\n updateStatus(orderId, newStatus) {\n const order = this.orders.find(o => o.orderId === orderId);\n if (order) {\n order.status = newStatus;\n this.emit('order:statusChanged', order);\n }\n }\n}\n\n// Observadores (Observers)\nfunction logObserver(order) {\n console.log(`[LOG] Order ${order.orderId}: status changed to ${order.status} at ${new Date().toISOString()}`);\n}\n\nfunction emailObserver(order) {\n console.log(`[EMAIL] Sending notification for order ${order.orderId} with status ${order.status}`);\n}\n\nfunction analyticsObserver(order) {\n console.log(`[ANALYTICS] Recording event: order ${order.orderId} -> ${order.status}`);\n}\n\n// Uso\nconst manager = new OrderManager();\nmanager.on('order:created', logObserver);\nmanager.on('order:statusChanged', logObserver);\nmanager.on('order:statusChanged', emailObserver);\nmanager.on('order:statusChanged', analyticsObserver);\n\nmanager.createOrder('ORD-001', ['Book', 'Pen']);\nmanager.updateStatus('ORD-001', 'shipped');\n\n\nEste enfoque permite agregar o eliminar observadores sin modificar el código del sujeto, lo que hace que el sistema sea flexible y fácilmente extensible.
\n\n2. Patrón Proxy (Apoderado): control de acceso y almacenamiento en caché
\n\nProxy proporciona un objeto sustituto que gestiona el acceso a otro objeto. En JavaScript, este patrón se implementa mediante el objeto incorporado Proxy, que permite interceptar y redefinir operaciones fundamentales (acceso a propiedades, llamadas a funciones, etc.).
Ejemplo: proxy de almacenamiento en caché para solicitudes API
\nSupongamos que tenemos un servicio que realiza costosas solicitudes a una API externa. Queremos almacenar en caché los resultados para evitar llamadas repetidas.
\n\n// Servicio real (Real Subject)\nclass ExternalApiService {\n async fetchUser(id) {\n console.log(`Fetching user ${id} from external API...`);\n // Simulación de retardo de red\n await new Promise(resolve => setTimeout(resolve, 1000));\n return { id, name: `User ${id}`, email: `user${id}@example.com` };\n }\n}\n\n// Proxy (Proxy)\nclass CachingProxy {\n constructor(realService) {\n this.realService = realService;\n this.cache = new M