Volver a JavaScript Intermedio

Patrones de Diseño en JavaScript

Los patrones de diseño son soluciones reutilizables a problemas comunes.

Patrón Módulo

Encapsula el estado privado con una API pública:

const GestorTareas = (() => { // Estado privado let tareas = []; let siguienteId = 1; // Helper privado function validar(tarea) { if (!tarea.titulo) throw new Error('El título es obligatorio'); } // API pública return { agregar(titulo, prioridad = 'media') { const tarea = { id: siguienteId++, titulo, prioridad, completada: false }; validar(tarea); tareas.push(tarea); return tarea; }, completar(id) { const tarea = tareas.find(t => t.id === id); if (tarea) tarea.completada = true; }, obtenerTodas() { return [...tareas]; }, // Copia para evitar mutación obtenerPendientes(){ return tareas.filter(t => !t.completada); }, }; })();

Observador / PubSub

Desacopla productores de consumidores:

class EmisorEventos { #listeners = new Map(); on(evento, callback) { if (!this.#listeners.has(evento)) this.#listeners.set(evento, []); this.#listeners.get(evento).push(callback); return () => this.off(evento, callback); // Retorna función de desuscripción } off(evento, callback) { const cbs = this.#listeners.get(evento) ?? []; this.#listeners.set(evento, cbs.filter(cb => cb !== callback)); } emit(evento, ...args) { (this.#listeners.get(evento) ?? []).forEach(cb => cb(...args)); } } const bus = new EmisorEventos(); const desuscribir = bus.on('login', usuario => console.log(`Bienvenido, ${usuario.nombre}`)); bus.emit('login', { nombre: 'Ana' }); desuscribir(); // Limpiar

Singleton

Garantiza que una clase tenga una única instancia:

class Configuracion { static #instancia = null; #ajustes = {}; static obtenerInstancia() { if (!Configuracion.#instancia) { Configuracion.#instancia = new Configuracion(); } return Configuracion.#instancia; } set(clave, valor) { this.#ajustes[clave] = valor; } get(clave) { return this.#ajustes[clave]; } } const cfg1 = Configuracion.obtenerInstancia(); const cfg2 = Configuracion.obtenerInstancia(); console.log(cfg1 === cfg2); // true — misma instancia

Patrón Estrategia

Intercambia algoritmos en tiempo de ejecución:

const ordenadores = { burbuja: (arr) => { /* bubble sort */ return arr; }, mezcla: (arr) => { /* merge sort */ return arr; }, rápido: (arr) => { /* quick sort */ return arr; }, }; class OrdenadorDatos { constructor(estrategia = 'rápido') { this.estrategia = estrategia; } ordenar(datos) { return ordenadores[this.estrategia]([...datos]); } } const ordenador = new OrdenadorDatos('mezcla'); ordenador.ordenar([5, 3, 8, 1]); ordenador.estrategia = 'rápido'; // Cambiar estrategia en tiempo de ejecución

Patrón Constructor (Builder)

Construye objetos complejos paso a paso:

class ConstructorConsulta { #consulta = { tabla: '', condiciones: [], ordenPor: null, limite: null }; de(tabla) { this.#consulta.tabla = tabla; return this; } donde(condición) { this.#consulta.condiciones.push(condición); return this; } ordenarPor(col, dir = 'ASC') { this.#consulta.ordenPor = `${col} ${dir}`; return this; } limitar(n) { this.#consulta.limite = n; return this; } construir() { let sql = `SELECT * FROM ${this.#consulta.tabla}`; if (this.#consulta.condiciones.length) sql += ` WHERE ${this.#consulta.condiciones.join(' AND ')}`; if (this.#consulta.ordenPor) sql += ` ORDER BY ${this.#consulta.ordenPor}`; if (this.#consulta.limite) sql += ` LIMIT ${this.#consulta.limite}`; return sql; } } const consulta = new ConstructorConsulta() .de('usuarios') .donde('edad > 18') .donde('activo = true') .ordenarPor('nombre') .limitar(10) .construir();