Los patrones de diseño son soluciones estandarizadas para problemas recurrentes en la arquitectura de software. En el ecosistema de JavaScript, estos patrones no solo actúa como guías para escribir código más limpio, sino que también optimizan la mantenibilidad y la escalabilidad de las aplicaciones modernas.
Patrón Factory (Fábrica)
El patrón Factory se utiliza para centralizar la creación de objetos, ocultando la lógica de instanciación al cliente. Es ideal cuando el tipo exacto del objeto a crear se decide en tiempo de ejecución.
function FabricaDeVehiculos() {
this.crear = (tipo, marca) => {
switch (tipo) {
case "auto":
return new Auto(marca);
case "moto":
return new Moto(marca);
default:
throw new Error("Tipo de vehículo no soportado");
}
};
}
const Auto = function(marca) {
this.marca = marca;
this.ruedas = 4;
};
const Moto = function(marca) {
this.marca = marca;
this.ruedas = 2;
};
const fabrica = new FabricaDeVehiculos();
const miCoche = fabrica.crear("auto", "Tesla");
Patrón Singleton (Instancia Única)
El objetivo del Singleton es garantizar que una clase tenga una única instancia y proporcionar un punto de acceso global a ella. En JavaScript, esto se logra frecuentemente mediante el uso de cierres (closures) o módulos.
const GestorConfiguracion = (function() {
let instancia;
function crearInstancia() {
return {
tema: "oscuro",
idioma: "es"
};
}
return {
obtenerInstancia: function() {
if (!instancia) {
instancia = crearInstancia();
}
return instancia;
}
};
})();
const configA = GestorConfiguracion.obtenerInstancia();
const configB = GestorConfiguracion.obtenerInstancia();
console.log(configA === configB); // true
Patrón Adapter (Adaptador)
Este patrón permite que interfaces incompatibles trabajen juntas. Actúa como un traductor entre un sistema antiguo y uno nuevo o entre diferentes librerías de terceros.
// Interfaz antigua
function CalculadoraAntigua() {
this.operacion = function(v1, v2, operacion) {
if (operacion === "sumar") return v1 + v2;
};
}
// Interfaz nueva
function CalculadoraModerna() {
this.sumar = function(v1, v2) {
return v1 + v2;
};
}
// Adaptador
function AdaptadorCalculadora() {
const nuevaCalc = new CalculadoraModerna();
this.operacion = function(v1, v2, operacion) {
if (operacion === "sumar") {
return nuevaCalc.sumar(v1, v2);
}
};
}
const adaptador = new AdaptadorCalculadora();
console.log(adaptador.operacion(10, 5, "sumar"));
Patrón Decorator (Decorador)
El patrón Decorador permite añadir funcionalidades a un objeto de forma dinámica sin alterar su estructura original ni recurrir a la herencia compleja.
function Computadora() {
this.costo = function() { return 1000; };
}
function conMemoriaExtra(computadora) {
const costoBase = computadora.costo();
computadora.costo = function() {
return costoBase + 200;
};
}
function conDiscoSSD(computadora) {
const costoBase = computadora.costo();
computadora.costo = function() {
return costoBase + 150;
};
}
const miPc = new Computadora();
conMemoriaExtra(miPc);
conDiscoSSD(miPc);
console.log(miPc.costo()); // 1350
Patrón Proxy (Intermediario)
Un Proxy actúa como sustituto de otro objeto para controlar el acceso a este. Se utiliza comúnmente para validaciones, caché o registro de operaciones (logging).
const servicioAPI = {
obtenerDatos: function(id) {
console.log("Consultando base de datos para ID:", id);
return `Datos del ID ${id}`;
}
};
const proxyCache = new Proxy(servicioAPI, {
cache: {},
get: function(objetivo, propiedad) {
if (propiedad === 'obtenerDatos') {
return (id) => {
if (this.cache[id]) {
console.log("Retornando desde caché...");
return this.cache[id];
}
const resultado = objetivo[propiedad](id);
this.cache[id] = resultado;
return resultado;
};
}
}
});
proxyCache.obtenerDatos(1);
proxyCache.obtenerDatos(1); // Esta vez usa la caché
Patrón Facade (Fachada)
Proporciona una interfaz simplificada para un conjunto de interfaces más complejas en un subsistema. Es la base de muchas librerías que ocultan la complejidad interna tras métodos sencillos.
const SistemaMultimedia = {
audio: { encender: () => console.log("Audio listo") },
video: { reproducir: () => console.log("Video iniciado") },
luces: { atenuar: () => console.log("Luces bajas") }
};
const CineEnCasa = {
iniciarPelicula: function() {
SistemaMultimedia.luces.atenuar();
SistemaMultimedia.audio.encender();
SistemaMultimedia.video.reproducir();
}
};
CineEnCasa.iniciarPelicula();
Patrón Observer (Observador)
Define una relación de uno a muchos entre objetos, de modo que cuando un objeto cambia su estado, todos sus dependientes son notificados automáticamente.
class CanalNoticias {
constructor() {
this.suscriptores = [];
}
suscribir(observador) {
this.suscriptores.push(observador);
}
publicar(noticia) {
this.suscriptores.forEach(sub => sub.actualizar(noticia));
}
}
const lector1 = { actualizar: (n) => console.log("Lector 1 recibió:", n) };
const lector2 = { actualizar: (n) => console.log("Lector 2 recibió:", n) };
const bbc = new CanalNoticias();
bbc.suscribir(lector1);
bbc.suscribir(lector2);
bbc.publicar("Nueva actualización de JS disponible");
Patrón State (Estado)
Permite que un objeto altere su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar de clase.
class Semaforo {
constructor() {
this.estados = {
verde: { iluminar: () => console.log("Siga"), siguiente: 'amarillo' },
amarillo: { iluminar: () => console.log("Precaución"), siguiente: 'rojo' },
rojo: { iluminar: () => console.log("Alto"), siguiente: 'verde' }
};
this.estadoActual = this.estados.verde;
}
cambiar() {
this.estadoActual.iluminar();
this.estadoActual = this.estados[this.estadoActual.siguiente];
}
}
const transito = new Semaforo();
transito.cambiar(); // Siga
transito.cambiar(); // Precaución
Resumen de Diferencias Clave
- Adapter: Resuelve incompatibilidad de interfaces existentes.
- Proxy: Controla y envuelve el acceso al objeto original manteniendo la misma interfaz.
- Decorator: Extiende funcionalmente un objeto en tiempo de ejecución.
- Facade: Agrupa varios subsistemas bajo una interfaz única y simple.