Introducción: SOLID en el ecosistema JavaScript
SOLID es el acrónimo de cinco principios fundamentales del diseño orientado a objetos, formulados por Robert Martin. En el contexto de JavaScript, especialmente al desarrollar en Node.js con TypeScript y frameworks modernos (NestJS, Express), estos principios se vuelven críticamente importantes para construir aplicaciones escalables y mantenibles. A nivel avanzado, SOLID va más allá de clases e interfaces simples, abordando la arquitectura de módulos, la inyección de dependencias y las pruebas.
En este artículo, analizaremos cada principio SOLID con ejemplos de código para Node.js, centrándonos en los matices prácticos y los errores típicos. Aprenderás cómo aplicar SOLID en proyectos reales utilizando las capacidades de JavaScript (ES6+) y TypeScript.
1. Principio de responsabilidad única (SRP) en JavaScript
Teoría: Una clase o módulo debe tener solo una razón para cambiar. En JavaScript, esto a menudo se viola cuando en un mismo archivo se mezclan la lógica de negocio, el trabajo con la base de datos y el formateo de respuestas.
Práctica (Node.js): Separamos la responsabilidad a nivel de servicios y repositorios.
// ❌ Violación de SRP: la clase procesa solicitudes, valida y guardaclass UserService { createUser(data) { if (!data.email) throw new Error('Email requerido'); const db = new Database(); return db.save('users', data); }}
// ✅ Cumplimiento de SRP: separación en validador, repositorio y servicioclass UserValidator { validate(data) { if (!data.email) throw new Error('Email requerido'); }}
class UserRepository { constructor(db) { this.db = db; } save(data) { return this.db.save('users', data); }}
class UserService { constructor(validator, repository) { this.validator = validator; this.repository = repository; } createUser(data) { this.validator.validate(data); return this.repository.save(data); }}Utiliza inyección de dependencias (DI) para pasar el validador y el repositorio al servicio. Esto simplifica las pruebas y el reemplazo de componentes.
2. Principio de abierto/cerrado (OCP) a través del polimorfismo
Teoría: Las clases deben estar abiertas para la extensión, pero cerradas para la modificación. En JavaScript, esto se logra mediante herencia, composición o estrategias.
Práctica (Node.js con TypeScript): Usamos clases abstractas o interfaces para definir un contrato.
// Clase abstracta para informesabstract class ReportGenerator { abstract generate(data: any): string;}
class PDFReport extends ReportGenerator { generate(data: any): string { return `PDF: ${JSON.stringify(data)}`; }}
class CSVReport extends ReportGenerator { generate(data: any): string { return `CSV: ${data.join(',')}`; }}
// El código cliente no cambia al agregar nuevos formatosclass ReportService { constructor(private generator: ReportGenerator) {} createReport(data: any) { return this.generator.generate(data); }}Para JavaScript puro, utiliza fábricas o estrategias (patrón Strategy) para evitar modificar el código existente.
3. Principio de sustitución de Liskov (LSP) en código asíncrono
Teoría: Los objetos de una clase base deben ser reemplazables por objetos de una clase derivada sin alterar el funcionamiento del programa. En JavaScript, LSP a menudo se viola al sobrescribir métodos con diferentes contratos (por ejemplo, tipos de retorno o excepciones).
Práctica (Node.js con async/await): Asegúrate de que las subclases no debiliten las precondiciones ni refuercen las postcondiciones.
class Database { async connect() { return 'con