Los patrones de diseño son soluciones habituales a problemas comunes en el diseño de software. No son fragmentos de código que puedas copiar y pegar, sino conceptos o planos que puedes adaptar para resolver un problema de diseño particular en tu código.
Con el auge del desarrollo moderno en JavaScript y TypeScript (React, Node.js, NestJS), algunos patrones han cobrado más relevancia que otros. Esta guía cubre los más utilizados en la actualidad.
1. Patrones Creacionales
Se centran en cómo se crean los objetos, ocultando la lógica de creación en lugar de instanciar objetos directamente.
Singleton
Garantiza que una clase tenga una única instancia, proporcionando un punto de acceso global a ella.
Uso actual: Conexiones a bases de datos, gestores de estado global, configuraciones de la aplicación.
class Database {
private static instance: Database;
private constructor() {
// La inicialización real de la base de datos iría aquí
}
public static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
}
// Uso:
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // trueFactory (Fábrica)
Proporciona una interfaz para crear objetos, pero permite a las subclases o a la configuración alterar el tipo de objetos que se crearán.
Uso actual: Creación dinámica de componentes de UI (ej. HOCs en React), instanciación de diferentes tipos de usuarios o servicios.
// Ejemplo de Factory para crear diferentes tipos de notificaciones
function createNotification(type) {
switch(type) {
case 'email':
return new EmailNotification();
case 'sms':
return new SMSNotification();
case 'push':
return new PushNotification();
default:
throw new Error('Tipo de notificación no soportado');
}
}💡 Caso práctico: Si quieres ver cómo el patrón Factory ayuda a evitar la repetición de código en React, revisa nuestro Ejemplo Práctico: Patrón Factory en un panel de administración.
2. Patrones Estructurales
Se ocupan de cómo se componen las clases y objetos para formar estructuras más grandes, manteniendo la flexibilidad y eficiencia.
Decorator (Decorador)
Permite añadir comportamientos a objetos individuales de forma dinámica, sin afectar el comportamiento de otros objetos de la misma clase.
Uso actual: Muy común en frameworks como NestJS o Angular (ej. @Controller(), @Injectable()), y en React mediante Higher-Order Components (HOC).
// En TypeScript, los decoradores son una funcionalidad nativa (experimental/stage 3)
function LogExecutionTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.time(propertyKey);
const result = originalMethod.apply(this, args);
console.timeEnd(propertyKey);
return result;
};
}
class MathOperations {
@LogExecutionTime
add(a: number, b: number) {
return a + b;
}
}💡 Caso práctico: Para profundizar en el patrón Decorator y ver su aplicación real interceptando y decorando enlaces MDX en React/Next.js, consulta nuestro Ejemplo Práctico: Patrón Decorator.
Adapter (Adaptador)
Permite que interfaces incompatibles trabajen juntas. Actúa como un puente entre dos objetos.
Uso actual: Integración de APIs de terceros. Si la API externa cambia, solo necesitas actualizar el adaptador, protegiendo el resto de tu código.
// API de Terceros
class StripeAPI {
makePayment(amountInCents) { /* ... */ }
}
// Nuestro Adaptador
class StripeAdapter {
constructor() {
this.stripe = new StripeAPI();
}
// Adaptamos el método para que coincida con nuestra interfaz interna
pay(amountInEuros) {
const amountInCents = amountInEuros * 100;
return this.stripe.makePayment(amountInCents);
}
}3. Patrones de Comportamiento
Se centran en la comunicación eficiente y la asignación de responsabilidades entre objetos.
Observer (Observador)
Define una dependencia de uno-a-muchos entre objetos. Cuando el objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.
Uso actual: La base de la programación reactiva (RxJS), la gestión del estado en React (Redux, Zustand, Context API), y la emisión de eventos de Node.js (EventEmitter).
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} recibió la notificación:`, data);
}
}
// Uso
const newsletter = new Subject();
const subscriber1 = new Observer('Alice');
newsletter.subscribe(subscriber1);
newsletter.notify('Nuevo artículo publicado!');Strategy (Estrategia)
Permite definir una familia de algoritmos, encapsular cada uno y hacerlos intercambrafoables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan.
Uso actual: Sistemas de autenticación (ej. Passport.js con estrategias locales, de Google o GitHub), cálculo de descuentos en e-commerce, o diferentes métodos de ordenación.
interface AuthStrategy {
authenticate(credentials: any): boolean;
}
class JWTStrategy implements AuthStrategy {
authenticate(credentials: any) {
// Lógica para JWT
return true;
}
}
class OAuthStrategy implements AuthStrategy {
authenticate(credentials: any) {
// Lógica para OAuth
return true;
}
}
class AuthContext {
private strategy: AuthStrategy;
constructor(strategy: AuthStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: AuthStrategy) {
this.strategy = strategy;
}
login(credentials: any) {
return this.strategy.authenticate(credentials);
}
}Conclusión
Conocer estos patrones te proporcionará un vocabulario común con otros desarrolladores y soluciones probadas para problemas recurrentes. Recuerda la regla de oro (YAGNIGlosarioYAGNI (You Aren't Gonna Need It)Principio de Extreme Programming (XP) que establece que un programador no debe añadir funcionalidad hasta que sea estrictamente necesario. Su objetivo es evitar la sobre-ingeniería (over-engineering), previniendo código para funcionalidades hipotéticas.Ver término completo →): no apliques un patrón a menos que realmente necesites la flexibilidad que proporciona, para evitar caer en la sobre-ingeniería.