Introducción: Por qué SOLID es importante para el desarrollador de TypeScript
SOLID son cinco principios fundamentales de la programación orientada a objetos, formulados por Robert Martin (Tío Bob). En el contexto de TypeScript, que combina el tipado estático y la flexibilidad de JavaScript, la aplicación de los principios SOLID permite crear aplicaciones escalables, testeables y fáciles de mantener. En este artículo analizaremos cada principio con una base teórica y ejemplos prácticos en TypeScript.
1. Single Responsibility Principle (SRP) — Principio de responsabilidad única
Teoría
Una clase debe tener una sola razón para cambiar. Esto significa que cada módulo, clase o función es responsable de exactamente una funcionalidad.
Práctica en TypeScript
Consideremos la violación de SRP con el ejemplo de una clase que gestiona usuarios y el envío de notificaciones:
// ❌ Violación de SRPclass UserManager { constructor(private name: string, private email: string) {}
saveUser(): void { console.log(`Usuario ${this.name} guardado en la BD`); }
sendWelcomeEmail(): void { console.log(`Correo de bienvenida enviado a ${this.email}`); }}Versión corregida con separación de responsabilidades:
// ✅ Cumplimiento de SRPclass User { constructor(public name: string, public email: string) {}}
class UserRepository { save(user: User): void { console.log(`Usuario ${user.name} guardado en la BD`); }}
class EmailService { sendWelcomeEmail(user: User): void { console.log(`Correo de bienvenida enviado a ${user.email}`); }}2. Open/Closed Principle (OCP) — Principio de abierto/cerrado
Teoría
Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación. En otras palabras, debe poder agregar nueva funcionalidad sin cambiar el código existente.
Práctica en TypeScript
Ejemplo con cálculo de descuentos para diferentes tipos de clientes:
// ❌ Violación de OCPclass DiscountCalculator { calculateDiscount(customerType: string, amount: number): number { if (customerType === 'regular') { return amount * 0.1; } else if (customerType === 'premium') { return amount * 0.2; } else if (customerType === 'vip') { return amount * 0.3; } return 0; }}// ✅ Cumplimiento de OCP mediante polimorfismointerface DiscountStrategy { calculate(amount: number): number;}
class RegularDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.1; }}
class PremiumDiscount implements DiscountStrategy { calculate(amount: number): number { return amount * 0.2; }}
class DiscountCalculator { constructor(private strategy: DiscountStrategy) {}
calculate(amount: number): number { return this.strategy.calculate(amount); }}3. Liskov Substitution Principle (LSP) — Principio de sustitución de Barbara Liskov
Teoría
Los objetos de un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. En palabras simples: si tiene una clase base, cualquier heredero debe funcionar correctamente en todos los lugares donde se use la clase base.
Práctica en TypeScript
Ejemplo clásico con figuras geométricas:
// ❌ Violación de LSPclass Rectangle { constructor(protected width: number, protected height: number) {}
setWidth(width: number): void { this.width = width; }
setHeight(height: number): void { this.height = height; }
getArea(): number { return this.width * this.height; }}
class Square extends Rectangle { setWidth(width: number): void { this.width = width; this.height = width; // ¡Violación! }
setHeight(height: number): vo