Principios SOLID en Kotlin: teoría y práctica a nivel avanzado
SOLID es un conjunto fundamental de principios de diseño orientado a objetos, introducido por Robert Martin. En el contexto de Kotlin, que combina las mejores características de la POO y la programación funcional, estos principios adquieren nuevas posibilidades de implementación. En este artículo, examinaremos cada principio a nivel avanzado, utilizando las capacidades únicas del lenguaje: funciones inline, delegación, clases selladas y funciones de extensión.
1. Principio de Responsabilidad Única (SRP) y delegación
El principio de responsabilidad única establece: una clase debe tener solo una razón para cambiar. En Kotlin, para cumplir con SRP, se utiliza a menudo el patrón Delegación (Delegation). La palabra clave by permite delegar automáticamente la implementación de una interfaz a otro objeto.
Problema: La clase UserService es responsable de la lógica de negocio, el registro y el envío de correos electrónicos.
// ❌ Violación de SRPclass UserService { fun registerUser(email: String, password: String) { // Lógica de negocio println("Usuario registrado") // Registro log("Usuario registrado: $email") // Envío de correo sendEmail(email, "¡Bienvenido!") }
private fun log(message: String) { /* ... */ } private fun sendEmail(to: String, body: String) { /* ... */ }}Solución usando delegación:
// ✅ Cumplimiento de SRP mediante delegacióninterface Logger { fun log(message: String)}
interface EmailSender { fun send(to: String, body: String)}
class ConsoleLogger : Logger { override fun log(message: String) = println("LOG: $message")}
class SmtpEmailSender : EmailSender { override fun send(to: String, body: String) = println("Enviando correo a $to: $body")}
class UserService( private val logger: Logger, private val emailSender: EmailSender) : Logger by logger, EmailSender by emailSender { // 👈 Delegación
fun registerUser(email: String, password: String) { println("Usuario registrado") log("Usuario registrado: $email") send(email, "¡Bienvenido!") }}
// Usofun main() { val service = UserService(ConsoleLogger(), SmtpEmailSender()) service.registerUser("user@example.com", "pass123")}El uso de by permite evitar código repetitivo y separar claramente las responsabilidades. La clase UserService ahora solo se encarga del registro, mientras que el registro y el envío se delegan a clases separadas.
2. Principio Abierto/Cerrado (OCP) y clases selladas
El principio abierto/cerrado: las clases deben estar abiertas para la extensión, pero cerradas para la modificación. En Kotlin, las clases selladas y las funciones de extensión son ideales para esto.
Problema: La función processPayment usa when con verificación de tipo, y al agregar un nuevo método de pago, debe modificarse.
// ❌ Violación de OCPfun processPayment(type: String, amount: Double) { when (type) { "card" -> println("Procesando pago con tarjeta: $amount") "paypal" -> println("Procesando pago con PayPal: $amount") else -> throw IllegalArgumentException("Tipo de pago desconocido") }}Solución con clases selladas:
// ✅ Cumplimiento de OCPsealed class PaymentMethod { data class Card(val number: String, val cvv: String) : PaymentMethod() data class PayPal(val email: String) : PaymentMethod() object Cash : PaymentMethod()}
// Función cerrada para modificaciónfun processPayment(method: PaymentMethod, amount: Double) { // Cuando aparezcan nuevas subclases, el compilador requerirá su manejo