Introducción: ¿por qué SOLID es importante en Kotlin?
SOLID es el acrónimo de cinco principios fundamentales de la programación orientada a objetos, formulados por Robert Martin (tío Bob). En el ecosistema de Kotlin, que combina lo mejor de la programación orientada a objetos y funcional, cumplir con los principios SOLID ayuda a crear código flexible, testeable y mantenible. Estos principios son especialmente relevantes para el desarrollo Android, aplicaciones de servidor con Ktor y sistemas multiproceso con corrutinas.
En este artículo analizaremos cada uno de los cinco principios SOLID con ejemplos adaptados a los modismos de Kotlin. Aprenderá a evitar errores típicos y a hacer que su código no solo funcione, sino que sea verdaderamente de calidad.
1. Single Responsibility Principle (SRP) — principio de responsabilidad única
Esencia del principio
Una clase debe tener solo una razón para cambiar. En otras palabras, cada clase es responsable de exactamente una funcionalidad.
Ejemplo incorrecto (violación de SRP)
class UserManager { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ } fun sendEmail(to: String, subject: String) { /* ... */ } fun generateReport(userId: Int): String { /* ... */ }}Aquí UserManager es responsable tanto de la gestión de usuarios como del envío de correos y la generación de informes. Un cambio en la lógica del correo implicaría modificar esta clase, aunque no debería ocuparse del correo.
Ejemplo correcto (cumplimiento de SRP)
class UserRepository { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ }}
class EmailService { fun sendEmail(to: String, subject: String) { /* ... */ }}
class ReportGenerator { fun generateReport(userId: Int): String { /* ... */ }}Ahora cada clase tiene una sola razón para cambiar. Esto simplifica las pruebas y el mantenimiento del código.
2. Open-Closed Principle (OCP) — principio de abierto/cerrado
Esencia del principio
Las entidades de software (clases, módulos, funciones) deben estar abiertas para la extensión, pero cerradas para la modificación. Esto significa que el nuevo comportamiento se añade mediante herencia o composición, no modificando el código existente.
Ejemplo incorrecto (violación de OCP)
class PaymentProcessor { fun process(paymentType: String) { when (paymentType) { "credit_card" -> // procesar tarjeta de crédito "paypal" -> // procesar PayPal else -> throw IllegalArgumentException() } }}Al añadir un nuevo método de pago (por ejemplo, Apple Pay), habría que modificar PaymentProcessor, lo que viola OCP.
Ejemplo correcto (cumplimiento de OCP)
interface PaymentMethod { fun process(amount: Double)}
class CreditCardPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PayPalPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PaymentProcessor(private val method: PaymentMethod) { fun processPayment(amount: Double) { method.process(amount) }}Ahora, para añadir un nuevo método de pago, basta con crear una clase que implemente PaymentMethod, sin tocar el código existente.
3. Liskov Substitution Principle (LSP) — principio de sustitución de Barbara Liskov
Esencia del principio
Los objetos de un programa deben ser reemplazables por instancias de sus subtipos sin alterar la corrección del programa. En términos simples, si tiene una clase Bird y una subclase Penguin, el código que funciona con Bird no debe fallar al sustituirlo por Penguin.
Ejemplo incorrecto (violación de LSP)
open class Rectangle { open var width: Int = 0