Principios SOLID
Los principios SOLID son un acrónimo de 5 valores de diseño:
- Single Responsability Principle(S) - Principio de Responsabilidad Única
- Open-Closed Principle(O) - Principio de Abierto-Cerrado
- Liskov Substitution Principle(L) - Principio de Sustitución de Liskov
- Interface Segregation Principle(I) - Principio de Segregación de la Interfaz
- Dependency Inversion Principle(D) - Principio de Inversión de Dependencias
Principio de Responsabilidad Única (S)
Según este principio, una clase debe tener una única tarea o responsabilidad. Para este ejemplo pensemos en una orquesta. Cada músico tiene un instrumento que sabe tocar perfectamente. Si ponemos tocar al violinista el tambor, seguro que no se escucharía bien.
Otro ejemplo, tenemos un robot que sabe hacer lo siguiente:
- Cocinar
- Jardinero
- Pintor
- Conductor
Este es el claro ejemplo de que nuestra clase no sigue el Principio de Responsabilidad Única. En cambio, si tenemos 4 robots y cada uno hace una de las funciones, estaría perfecto.
function calculateSalary(employee) { let salary = employee.hoursWorked * employee.hourlyRate; let report = ""; /* Generamos el reporte */ console.log(report); return salary; } function calculateSalary(employee) { return employee.hoursWorked * employee.hourlyRate; }
function generateReport(employee, salary) { let report = /*...*/; console.log(report); }Ahora cada función tiene su propia responsabilidad, lo que facilita su comprensión y mantenimiento.
Principio de abierto/cerrado (O)
Su definición oficial es que las entidades de software (clases, módulos, funciones, etc) deben estar abiertas para su extensión, pero cerradas para su modificación
Imaginemos un coche de juguete que se carga con baterías. Su diseño está cerrado para modificaciones, no puedes cambiar la forma en la que se carga. Sin embargo, está abierto para su extensión, puedes cargarlo con diferentes tipos de baterías o pilas
Imagina, por ejemplo, que tienes una aplicación de comercio electrónico con una función processPayment(), que recibe un monto y los detalles de una tarjeta de crédito. En un principio, este sistema permitía sólo pagos con tarjeta de crédito.
function processPayment(price, cardDetails) { /*...*/ console.log('Pagado con tarjeta.'); }Una buena solución sería agregar una nueva función processPaymentWithPayPal(). De esta manera, puedes manejar los pagos con PayPal sin alterar el código existente.
function processPaymentWithPayPal(price, accountDetails) { /*...*/ console.log('Pagado con PayPal.');}Principio de Sustitución de Liskov (L)
El principio de Sustitución de Liskov establece que los objetos de una superclase deben ser reemplazables por objetos de una subclase sin afectar la corrección del programa.
Si una clase hija no puede hacer las mismas acciones que una clase padre, puede causar bugs.
// Función de ejemplo que realizac una petición HTTPfunction makeRequest(url, errorHandler) { fetch(url) .then((response) => response.json()) .catch((error) => errorHandler.handle(error));}
// Podemos tener varias funciones para manejar erroresconst consoleErrorHandler = function handle(error) { console.log(error);};
const externalErrorHandler = function handle(error) { sendErrorToExternalService(error);};// Usando el principio de sustitución de Liskov,// podríamos pasar cualquier función manejadora de// errores durante una request.makeRequest(url, consoleErrorHandler);makeRequest(url, externalErrorHandler);Principio de segregación de la Interfaz (I)
Ninguna clase debería ser forzada a implementar interfaces o métodos que no va a utilizar.
Es mejor tener interfaces específicas, en lugar de tener una sola interfaz general. Esto también aplica a las funciones de JavaScript
No todos los robots tienen antenas, por lo que estamos implementando una función que algunos robots no van a utilizar
Un ejemplo con código:
class Product { constructor() { /* */ }
getDetails() { /* */ } saveToDb() {/* */ } displayInFrontEnd() { /* */ } }
// DigitalProduct no necesita el método saveToDb(), // sin embargo, lo hereda sin poder evitarlo // Se viola el principio de segregación de la interfaz
class DigitalProduct extends Product{ // Se hereda el método innecesario saveToDb()} } class Product { constructor() { /* */ }
getDetails() { /* */ } displayInFrontEnd() { /* */ } }
class PhysicalProduct extends Product { constructor() { super() } saveToDb() {/* */} }
class DigitalProduct extends Product{ // No se hereda el método innecesario saveToDb() }Principio de inversión de dependencia (D)
Este principio sostiene que los módulos de alto nivel, es decir, los módulos que contienen las decisiones estratégicas y las directivas de alto nivel, no deben depender de los módulos de bajo nivel, que son módulos que contienen la lógica detallada y de bajo nivel.
class EmailSender { sendEmail(message: string): void { console.log(`Enviando correo electrónico: ${message}`); }}
class Notification {private emailSender: EmailSender;
constructor() {this.emailSender = new EmailSender();}
sendNotification(message: string): void {this.emailSender.sendEmail(message);}}
// Usoconst notification = new Notification();notification.sendNotification('Hola, este es un mensaje');// Interfaz que define un método genérico para enviar mensajesinterface MessageSender { send(message: string): void;}
// Implementación para enviar mensajes por correo electrónicoclass EmailSender implements MessageSender { send(message: string): void { console.log(`Enviando correo electrónico: ${message}`); }}
// Clase que depende de la interfaz (abstracción) y no de una implementación concretaclass Notification { private sender: MessageSender;
constructor(sender: MessageSender) { this.sender = sender;}
sendNotification(message: string): void { this.sender.send(message);}
// Usoconst emailSender = new EmailSender();const notification = new Notification(emailSender);notification.sendNotification('Hola, este es un mensaje');