Principios SOLID en TypeScript: teoría y práctica de nivel avanzado
SOLID son cinco principios fundamentales de diseño orientado a objetos que permiten crear código flexible, mantenible y escalable. En TypeScript, gracias al tipado estricto, las interfaces y las clases abstractas, la implementación de estos principios se vuelve especialmente efectiva. En este artículo, analizaremos cada principio a nivel avanzado con ejemplos reales en TypeScript.
1. Single Responsibility Principle (SRP) — Principio de responsabilidad única
Teoría: Cada clase debe tener una única razón para cambiar. En TypeScript, esto significa que un módulo es responsable de exactamente una funcionalidad.
Práctica avanzada: A menudo, la violación del SRP se manifiesta en "objetos divinos" (God Objects). Utilice composición y delegación.
// ❌ Violación de SRP: la clase es responsable de datos, validación y almacenamientoclass UserService { constructor(private user: User) {}
validateEmail(email: string): boolean { return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email); }
saveToDatabase(): void { // lógica de almacenamiento }
sendWelcomeEmail(): void { // lógica de envío }}
// ✅ SRP: dividimos responsabilidadesinterface IEmailValidator { isValid(email: string): boolean;}
interface IUserRepository { save(user: User): Promise<void>;}
interface INotificationService { sendWelcome(user: User): Promise<void>;}
class UserRegistrationService { constructor( private validator: IEmailValidator, private repository: IUserRepository, private notifier: INotificationService ) {}
async register(user: User): Promise<void> { if (!this.validator.isValid(user.email)) { throw new Error('Invalid email'); } await this.repository.save(user); await this.notifier.sendWelcome(user); }}2. Open/Closed Principle (OCP) — Principio de abierto/cerrado
Teoría: Las clases deben estar abiertas para la extensión, pero cerradas para la modificación. En TypeScript, esto se logra mediante clases abstractas e interfaces.
Práctica avanzada: Utilice el patrón "Estrategia" (Strategy) para implementar OCP.
// ❌ Violación de OCP: al agregar un nuevo tipo de informe, es necesario modificar la claseclass ReportGenerator { generate(data: any[], type: string): string { if (type === 'csv') { return data.map(row => row.join(',')).join(''); } else if (type === 'json') { return JSON.stringify(data); } // al agregar 'xml' — modificamos este método throw new Error('Unsupported format'); }}
// ✅ OCP: usamos polimorfismointerface IReportFormatter { format(data: any[]): string;}
class CsvFormatter implements IReportFormatter { format(data: any[]): string { return data.map(row => row.join(',')).join(''); }}
class JsonFormatter implements IReportFormatter { format(data: any[]): string { return JSON.stringify(data); }}
class XmlFormatter implements IReportFormatter { format(data: any[]): string { // implementación para XML return '<data>...</data>'; }}
class ReportGeneratorOCP { constructor(private formatter: IReportFormatter) {}
generate(data: any[]): string { return this.formatter.format(data); }}
// Uso: extendemos sin modificarconst csvReport = new ReportGeneratorOCP(new CsvFormatter());const jsonReport = new ReportGeneratorOCP(new JsonFormatter());3. Liskov Substitution Principle (LSP) — Principio de sustitución de Barbara Liskov
Teoría: Los objetos en un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. En TypeScript, esto es especialmente crítico debido al tipado nominal.