Principios SOLID en JavaScript: de la teoría a la práctica en Node.js
SOLID son cinco principios fundamentales de diseño orientado a objetos, formulados por Robert Martin (tío Bob). Estos principios ayudan a crear código flexible, mantenible y escalable. Aunque muchos asocian SOLID con lenguajes OOP clásicos (Java, C#), también se aplican perfectamente en JavaScript, especialmente al desarrollar en Node.js. En este artículo, analizaremos cada principio con ejemplos concretos.
Principio de Responsabilidad Única (Single Responsibility Principle, SRP)
El SRP establece: "Una clase (o módulo) debe tener solo una razón para cambiar". Esto significa que cada módulo es responsable de una tarea específica.
Ejemplo incorrecto (violación del SRP):
class UserService { createUser(data) { /* lógica de creación */ } sendWelcomeEmail(email) { /* envío de correo */ } logActivity(action) { /* registro de actividad */ }}Aquí, una sola clase se encarga de la gestión de usuarios, el envío de correos y el registro de actividad. Si cambia el formato de los registros, habría que modificar UserService — aunque su tarea principal no haya cambiado.
Ejemplo correcto (cumplimiento del SRP):
class UserRepository { createUser(data) { /* solo trabajo con la BD */ }}
class EmailService { sendWelcomeEmail(email) { /* solo envío */ }}
class Logger { logActivity(action) { /* solo registro */ }}
class UserService { constructor(userRepo, emailService, logger) { this.userRepo = userRepo; this.emailService = emailService; this.logger = logger; } createUser(data) { const user = this.userRepo.createUser(data); this.emailService.sendWelcomeEmail(user.email); this.logger.logActivity('User created'); return user; }}Ahora cada clase es responsable de su propia área. Al combinarlas en UserService, obtenemos flexibilidad y aislamiento de los cambios.
Principio de Abierto/Cerrado (Open/Closed Principle, OCP)
El OCP establece: "Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación". Es decir, debemos poder agregar nueva funcionalidad sin modificar el código existente.
Ejemplo incorrecto (violación del OCP):
class PaymentProcessor { processPayment(type, amount) { if (type === 'card') { /* procesar tarjeta */ } else if (type === 'paypal') { /* procesar PayPal */ } // Al agregar un nuevo tipo, hay que modificar este método }}Ejemplo correcto (cumplimiento del OCP):
class PaymentStrategy { pay(amount) { throw new Error('Must be implemented'); }}
class CardPayment extends PaymentStrategy { pay(amount) { console.log(`Paid ${amount} via Card`); }}
class PayPalPayment extends PaymentStrategy { pay(amount) { console.log(`Paid ${amount} via PayPal`); }}
class PaymentProcessor { constructor(strategy) { this.strategy = strategy; } processPayment(amount) { this.strategy.pay(amount); }}
// Usoconst cardPayment = new PaymentProcessor(new CardPayment());cardPayment.processPayment(100);
const paypalPayment = new PaymentProcessor(new PayPalPayment());paypalPayment.processPayment(200);Ahora, para agregar un nuevo método de pago, basta con crear una nueva clase que herede de PaymentStrategy y pasarla a PaymentProcessor. El código original no se modifica.
Principio de Sustitución de Liskov (Liskov Substitution Principle, LSP)
El LSP establece: "Los objetos en un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa". En otras palabras, si tienes una base
```