Principios SOLID en JavaScript: teoría y práctica de la A a la Z
\n\nEl desarrollo de software de calidad requiere no solo conocer la sintaxis del lenguaje, sino también comprender los principios de arquitectura. SOLID son cinco principios fundamentales del diseño orientado a objetos que ayudan a crear código flexible, mantenible y escalable. En este artículo, analizaremos cada principio SOLID en el contexto de JavaScript (Node.js) con ejemplos prácticos.
\n\n¿Qué es SOLID?
\n\nEl acrónimo SOLID fue introducido por Robert Martin (Tío Bob) e incluye los siguientes principios:
\n\n- \n
- S — Single Responsibility Principle (Principio de responsabilidad única) \n
- O — Open/Closed Principle (Principio de abierto/cerrado) \n
- L — Liskov Substitution Principle (Principio de sustitución de Liskov) \n
- I — Interface Segregation Principle (Principio de segregación de interfaces) \n
- D — Dependency Inversion Principle (Principio de inversión de dependencias) \n
Estos principios son universales y aplicables a cualquier lenguaje de programación, incluido JavaScript. Veamos cada uno de ellos en detalle.
\n\n1. Single Responsibility Principle (SRP)
\n\nDefinición: Una clase o módulo debe tener solo una razón para cambiar. En otras palabras, cada módulo es responsable de una tarea específica.
\n\nEjemplo incorrecto: La clase UserService, que se encarga de guardar el usuario, enviar un email y generar informes.
// ❌ Violación de SRP\nclass UserService {\n saveUser(user) {\n // Guardar en la base de datos\n console.log('Usuario guardado');\n }\n\n sendEmail(user) {\n // Enviar email\n console.log('Email enviado a ' + user.email);\n }\n\n generateReport(user) {\n // Generar informe\n console.log('Informe generado para ' + user.name);\n }\n}\n\nEjemplo correcto: Dividimos la responsabilidad en clases separadas.
\n\n// ✅ Cumplimiento de SRP\nclass UserRepository {\n save(user) {\n console.log('Usuario guardado');\n }\n}\n\nclass EmailService {\n send(user) {\n console.log('Email enviado a ' + user.email);\n }\n}\n\nclass ReportGenerator {\n generate(user) {\n console.log('Informe generado para ' + user.name);\n }\n}\n\n// Uso\nconst user = { name: 'Iván', email: 'ivan@example.com' };\nconst userRepo = new UserRepository();\nconst emailService = new EmailService();\nconst reportGen = new ReportGenerator();\n\nuserRepo.save(user);\nemailService.send(user);\nreportGen.generate(user);\n\nAhora cada clase tiene una única responsabilidad, y los cambios en un módulo no afectan a los demás.
\n\n2. Open/Closed Principle (OCP)
\n\nDefinición: Las entidades de software (clases, módulos, funciones) deben estar abiertas para la extensión, pero cerradas para la modificación.
\n\nEjemplo incorrecto: Una función que calcula el área de figuras usando condicionales.
\n\n// ❌ Violación de OCP\nfunction calculateArea(shape) {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n // Al agregar una nueva figura, hay que modificar la función\n return 0;\n}\n\nEjemplo correcto: Usamos polimorfismo y herencia.
\n\n// ✅ Cumplimiento de OCP\nclass Shape {\n area() {\n throw new Error('El método area() debe ser implementado');\n }\n}\n\nclass Circle extends Shape {\n constructor(radius) {\n super();\n this.radius = radius;\n }\n\n area() {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle extends Shape {\n constructor