Aplicando principios de SOLID en JavaScript: varios ejemplos

En este artículo exploraremos los principios de SOLID, fundamentales para el desarrollo de software, y cómo estos pueden aplicarse en JavaScript para mejorar la calidad de nuestro código. A través de ejemplos prácticos, aprenderemos a escribir aplicaciones más mantenibles, escalables y flexibles.

Diagrama de principios SOLID en JavaScript

¿Qué son los principios SOLID?

SOLID es un acrónimo que representa cinco principios de la programación orientada a objetos diseñados para facilitar el desarrollo de software entendible, flexible y mantenible. Aunque originarios de la programación orientada a objetos, estos principios también son aplicables y beneficiosos en JavaScript, un lenguaje que, aunque no es estrictamente orientado a objetos, brinda estructuras compatibles con el paradigma como clases a partir de ES6.

Single Responsibility Principle (SRP)

El Principio de Responsabilidad Única postula que una clase o módulo debe tener una sola razón para cambiar. Esto significa que cada módulo o clase debe ser responsable de una parte específica de la funcionalidad proporcionada por el software, y esa responsabilidad debe estar completamente encapsulada por la clase. En JavaScript, esto a menudo se traduce en la creación de funciones puras y módulos que hacen una sola cosa y la hacen bien.

Open/Closed Principle (OCP)

El Principio Abierto/Cerrado nos dice que las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para la extensión, pero cerradas para la modificación. En la práctica, esto significa que debemos poder añadir nuevas funcionalidades sin alterar el código existente. JavaScript facilita este principio a través de patrones como el uso de funciones de orden superior y la composición de funciones.

Liskov Substitution Principle (LSP)

El Principio de Sustitución de Liskov establece que los objetos de un programa deben ser sustituibles por instancias de sus subtipos sin afectar la corrección del programa. En JavaScript, podemos adherirnos a este principio garantizando que las clases derivadas extiendan la funcionalidad de sus clases base sin alterar su comportamiento esperado.

Interface Segregation Principle (ISP)

El Principio de Segregación de Interfaces sugiere que los clientes no deben verse forzados a depender de interfaces que no usan. Aunque JavaScript no tiene interfaces en el sentido tradicional, podemos aplicar una idea similar evitando exponer métodos que no son relevantes para el cliente de un módulo o clase.

Dependency Inversion Principle (DIP)

Finalmente, el Principio de Inversión de Dependencias nos aconseja que deberíamos depender de abstracciones y no de concreciones. Esto implica que los detalles deben depender de las abstracciones. En JavaScript, este principio a menudo se implementa utilizando el patrón de inyección de dependencias, que ayuda a desacoplar los módulos y facilitar el testing y la mantenibilidad.

Ejemplo de Inversión de Dependencias en JavaScript

Ejemplos prácticos de SOLID en JavaScript

Aplicando SRP con Funciones Puras

En JavaScript, una función pura es una que, dado los mismos argumentos, siempre retorna el mismo resultado y no tiene efectos secundarios visibles. La implementación de funciones puras se alinea perfectamente con el Principio de Responsabilidad Única, ya que cada función está diseñada para realizar una tarea específica.

function calculateArea(width, height) {
 return width * height;
}

El código anterior es un ejemplo de función pura que calcula el área de una figura. Su única responsabilidad es devolver el área basada en el ancho y el alto proporcionados, haciéndola un claro ejemplo de SRP en acción.

Extendiendo Funcionalidades con OCP

Al trabajar con objetos o clases en JavaScript, podemos seguir el Principio Abierto/Cerrado usando el patrón de decorador para extender su comportamiento sin modificar el código original.

class Book {
 constructor(title, author) {
 this.title = title;
 this.author = author;
 }
 describe() {
 return `${this.title} written by ${this.author}`;
 }
}

class Ebook extends Book {
 constructor(title, author, format) {
 super(title, author);
 this.format = format;
 }
 describe() {
 return `${super.describe()} in ${this.format} format`;
 }
}

En este ejemplo, la clase Ebook extiende la clase Book. Sin alterar la clase base, extendemos su funcionalidad para incluir un formato, cumpliendo con OCP.

Garantizando LSP con Clases de JavaScript

Para asegurar que aplicamos el Principio de Sustitución de Liskov, nuestras clases derivadas deben ser capaces de reemplazar a sus clases base sin interrumpir la funcionalidad del programa.

class Bird {
 fly() {
 console.log('The bird is flying');
 }
}

class Duck extends Bird {
 quack() {
 console.log('The duck is quacking');
 }
}

const myBird = new Duck();
myBird.fly();
myBird.quack();

Aquí, nuestra clase Duck extiende Bird y añade un nuevo método quack. Cualquier instancia del Bird puede ser sustituida por Duck sin afectar la expectativa del comportamiento de fly.

Implementando ISP con Módulos

En JavaScript, podemos aplicar el Principio de Segregación de Interfaces desarrollando módulos que expongan solo la funcionalidad necesaria para el cliente.

const mathOperations = {
 sum(a, b) {
 return a + b;
 },
 subtract(a, b) {
 return a - b;
 }
};

export const { sum } = mathOperations;

En este fragmento, a pesar de que el módulo define sum y subtract, solo se exporta sum para el uso del cliente, proporcionando una aplicación directa del ISP.

DIP en la Práctica con Inyección de Dependencias

Al separar las construcciones de nuestras clases de sus dependencias, podemos seguir el Principio de Inversión de Dependencias eficazmente en JavaScript.

class Store {
 constructor(paymentProcessor) {
 this.paymentProcessor = paymentProcessor;
 }
 purchaseBike(quantity) {
 this.paymentProcessor.pay(200 * quantity);
 }
 purchaseHelmet(quantity) {
 this.paymentProcessor.pay(15 * quantity);
 }
}

class PaypalPaymentProcessor {
 pay(amount) {
 console.log(`Paying ${amount} using Paypal`);
 }
}

const store = new Store(new PaypalPaymentProcessor());
store.purchaseBike(2);
store.purchaseHelmet(2);

En esta implementación, la clase Store no está directamente acoplada a un método de pago específico, sino que a través de la inyección de dependencias, cualquier procesador de pago que siga la interfaz pay puede ser utilizado, cumpliendo con DIP.

Conclusión

La aplicación de los principios de SOLID en JavaScript es una parte esencial para crear código de calidad que sea robusto, mantenible y fácil de entender. Aunque estos principios se originaron en la programación orientada a objetos, su aplicación en JavaScript, un lenguaje de paradigma híbrido, demuestra su universalidad y relevancia en el desarrollo de software moderno.

Te puede interesar

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *