Principios SOLID en TypeScript: teoría y práctica de la A a la Z
\n\nSOLID es el acrónimo de cinco principios fundamentales de la programación orientada a objetos, formulados por Robert Martin (tío Bob). Estos principios ayudan a crear código flexible, mantenible y escalable. En este artículo, analizaremos en detalle cada principio SOLID con ejemplos en TypeScript, desde definiciones simples hasta escenarios de uso reales.
\n\n1. Single Responsibility Principle (SRP) — Principio de responsabilidad única
\n\nDefinición: Cada clase debe tener una única razón para cambiar. En otras palabras, la clase debe ser responsable de una tarea claramente definida.
\n\nProblema: Cuando una clase asume múltiples responsabilidades, se vuelve difícil de probar, extender y mantener. El cambio de una función puede romper otra.
\n\nEjemplo de violación de SRP:
\n// ❌ Violación de SRP: la clase se encarga del almacenamiento, formateo y envío\nclass UserService {\n saveUser(user: User): void {\n // Guardar en BD\n console.log('User saved');\n }\n\n formatUserData(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n\n sendWelcomeEmail(user: User): void {\n // Enviar email\n console.log('Email sent');\n }\n}\n\nSolución con SRP:
\n// ✅ Cada clase es responsable de su tarea\nclass UserRepository {\n save(user: User): void {\n console.log('User saved');\n }\n}\n\nclass UserFormatter {\n format(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n}\n\nclass EmailService {\n sendWelcome(user: User): void {\n console.log('Email sent');\n }\n}\n\n2. Open-Closed Principle (OCP) — Principio de abierto/cerrado
\n\nDefinición: Las clases deben estar abiertas para la extensión, pero cerradas para la modificación. Esto significa que debe poder agregar nueva funcionalidad sin cambiar el código existente.
\n\nEjemplo de violación de OCP:
\n// ❌ Al agregar un nuevo tipo de figura, habrá que modificar el método calculateArea\nclass AreaCalculator {\n calculateArea(shape: any): number {\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 throw new Error('Unknown shape');\n }\n}\n\nSolución con OCP (usamos polimorfismo):
\n// ✅ Usamos interfaz y herencia\ninterface Shape {\n area(): number;\n}\n\nclass Circle implements Shape {\n constructor(private radius: number) {}\n area(): number {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle implements Shape {\n constructor(private width: number, private height: number) {}\n area(): number {\n return this.width * this.height;\n }\n}\n\nclass AreaCalculator {\n calculateArea(shape: Shape): number {\n return shape.area(); // No requiere cambios al agregar nuevas figuras\n }\n}\n\n3. Liskov Substitution Principle (LSP) — Principio de sustitución de Liskov
\n\nDefinición: Los objetos de un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. La clase hija no debe violar el contrato de la clase padre.
\n\nEjemplo de violación de LSP:
\n// ❌ Violación: la subclase cambia el comportamiento del ancestro\nclass Bird {\n fly(): void {\n console.log('Flying');\n }\n}\n\nclass Penguin extends Bird {\n fly(): void {\n throw new Error('Penguins cannot fly!'); // Violación del contrato\n }\n}\n\nSolución con LSP:
\n// ✅ Separación