Introducción a los principios SOLID en JavaScript
SOLID es un acrónimo de cinco principios fundamentales de la programación orientada a objetos, formulados por Robert Martin (Tío Bob). A pesar de que JavaScript no fue originalmente un lenguaje OOP estrictamente tipado, el JavaScript moderno (ES6+) con clases, módulos y TypeScript utiliza activamente estos principios para crear aplicaciones escalables y mantenibles, especialmente en el entorno Node.js.
En este artículo analizaremos cada uno de los cinco principios SOLID con ejemplos prácticos de código en JavaScript. Comprender estos principios le ayudará a escribir código limpio, flexible y resistente a los cambios.
1. Single Responsibility Principle (SRP) — Principio de responsabilidad única
El principio establece: una clase o módulo debe tener solo una razón para cambiar. En otras palabras, cada módulo es responsable de una funcionalidad específica.
Violación del SRP
Consideremos la clase UserService, que simultáneamente gestiona usuarios, envía correos electrónicos y registra acciones:
class UserService { constructor() { this.users = []; }
addUser(name) { this.users.push({ name }); console.log('User added'); // Logging this.sendEmail(name); // Envío de email }
sendEmail(name) { console.log(`Email sent to ${name}`); }
getUsers() { return this.users; }}Aquí la clase tiene tres responsabilidades: gestión de datos, registro y envío de notificaciones. Un cambio en la lógica de registro o envío de correos electrónicos implicaría modificar la clase UserService.
Cumplimiento del SRP
Dividamos las responsabilidades en clases separadas:
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class EmailService { send(name) { console.log(`Email sent to ${name}`); }}
class UserService { constructor(logger, emailService) { this.users = []; this.logger = logger; this.emailService = emailService; }
addUser(name) { this.users.push({ name }); this.logger.log(`User ${name} added`); this.emailService.send(name); }
getUsers() { return this.users; }}Ahora cada clase se encarga de su propia tarea, y los cambios en un servicio no afectan a los demás.
2. Open-Closed Principle (OCP) — Principio de abierto/cerrado
El principio establece: las entidades de software deben estar abiertas para extensión, pero cerradas para modificación. Es decir, debemos poder agregar nueva funcionalidad sin modificar el código existente.
Violación del OCP
Supongamos que tenemos una función para calcular el área de figuras:
function calculateArea(shape) { if (shape.type === 'circle') { return Math.PI * shape.radius ** 2; } else if (shape.type === 'rectangle') { return shape.width * shape.height; } // Al agregar una nueva figura, habría que modificar esta función}Cumplimiento del OCP
Usamos polimorfismo a través de clases:
class Shape { area() { throw new Error('Method "area()" must be implemented'); }}
class Circle extends Shape { constructor(radius) { super(); this.radius = radius; } area() { return Math.PI * this.radius ** 2; }}
class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } area() { return this.width * this.height; }}
// Agregamos una nueva figura sin modificar el código existenteclass Triangle extends Shape { constructor(base, height) { super(); this.base = base; this.height = height; } area() { return (this.base * this.height) / 2; }}
function calculateArea