Principios SOLID en Kotlin: de la teoría a la práctica
\n\nSOLID es el acrónimo de cinco principios fundamentales de la programación orientada a objetos, formulados por Robert Martin. Estos principios ayudan a crear código flexible, mantenible y escalable. En este artículo, analizaremos en detalle cada principio SOLID con ejemplos del desarrollo real en Kotlin.
\n\nComprender SOLID es críticamente importante para cualquier desarrollador de Kotlin, especialmente al trabajar con frameworks como Spring Boot o Ktor. Estos principios son la base de la arquitectura limpia y los patrones de diseño.
\n\n1. Single Responsibility Principle (SRP) — Principio de responsabilidad única
\n\nEsencia: Cada clase debe tener una sola razón para cambiar. En otras palabras, la clase debe ser responsable de una única funcionalidad.
\n\nEn Kotlin, este principio es especialmente importante debido al soporte del estilo funcional. Veamos un mal ejemplo:
\n\n// ❌ Violación de SRP: la clase se ocupa tanto del registro como de la BD\nclass UserService {\n fun saveUser(user: User) {\n // Registro\n println("Saving user: ${user.name}")\n // Lógica de negocio\n validateUser(user)\n // Trabajo con BD\n database.insert(user)\n }\n}\n\n\nCorrijamos la situación dividiendo la responsabilidad:
\n\n// ✅ Enfoque correcto: cada responsabilidad en una clase separada\nclass Logger {\n fun log(message: String) = println("[LOG] $message")\n}\n\nclass UserValidator {\n fun validate(user: User): Boolean = user.name.isNotBlank()\n}\n\nclass UserRepository {\n fun save(user: User) { /* guardar en BD */ }\n}\n\nclass UserService(\n private val logger: Logger,\n private val validator: UserValidator,\n private val repository: UserRepository\n) {\n fun saveUser(user: User) {\n logger.log(\"Saving user: ${user.name}\")\n if (validator.validate(user)) {\n repository.save(user)\n }\n }\n}\n\n\n2. Open-Closed Principle (OCP) — Principio de abierto/cerrado
\n\nEsencia: Las clases deben estar abiertas para la extensión, pero cerradas para la modificación. La nueva funcionalidad debe agregarse mediante herencia o composición, sin modificar el código existente.
\n\nEn Kotlin, las clases selladas (sealed) y las interfaces son ideales para implementar OCP:
\n\n// ❌ Violación de OCP: al agregar un nuevo tipo de figura, hay que modificar la clase\nclass AreaCalculator {\n fun calculate(shape: Any): Double = when (shape) {\n is Circle -> 3.14 * shape.radius * shape.radius\n is Rectangle -> shape.width * shape.height\n else -> throw IllegalArgumentException()\n }\n}\n\n\nImplementación correcta usando polimorfismo:
\n\n// ✅ Enfoque correcto: las nuevas figuras se agregan sin modificar el código existente\ninterface Shape {\n fun area(): Double\n}\n\nclass Circle(val radius: Double) : Shape {\n override fun area() = Math.PI * radius * radius\n}\n\nclass Rectangle(val width: Double, val height: Double) : Shape {\n override fun area() = width * height\n}\n\n// Ahora se puede agregar un triángulo sin modificar AreaCalculator\nclass Triangle(val base: Double, val height: Double) : Shape {\n override fun area() = 0.5 * base * height\n}\n\nclass AreaCalculator {\n fun calculate(shape: Shape) = shape.area()\n}\n\n\n3. Liskov Substitution Principle (LSP) — Principio de sustitución de Barbara Liskov
\n\nEsencia: Los objetos de una clase base deben poder ser reemplazados por objetos de sus clases derivadas sin alterar el funcionamiento del programa. La subclase no debe debilitar las precondiciones ni fortalecer las postcondiciones.
\n\nVeamos el ejemplo clásico con un rectángulo y un cuadrado:
\n\n// ❌ Violación de LSP: el cuadrado no puede reemplazar al rectángulo\nopen class Rectangle(\n open var width: Int,\n open var height: Int\n) {\n fun area() = width * height\n}\n\nclass Square(sid