Patrones de diseño en Kotlin con ejemplos: de la A a la Z
Los patrones de diseño (design patterns) son soluciones probadas para problemas típicos de desarrollo. En Kotlin, gracias a su sintaxis concisa y potentes capacidades (data class, object, extension functions, corrutinas), muchos patrones clásicos se implementan de manera más simple y elegante que en Java. En este artículo analizaremos los patrones clave en Kotlin, desde los creacionales hasta los de comportamiento, con ejemplos de código reales.
1. Patrones creacionales (Creational)
Estos patrones gestionan el proceso de creación de objetos, haciendo que el sistema sea independiente de la forma en que se crean.
1.1. Singleton (Instancia única)
Garantiza que una clase tenga solo una instancia. En Kotlin, se utiliza la palabra clave object para esto.
object DatabaseConnection { val connectionString: String = "jdbc:postgresql://localhost:5432/mydb"
fun connect() { println("Connecting to $connectionString") }}
fun main() { DatabaseConnection.connect() println(DatabaseConnection.connectionString)}Cuándo usarlo: para gestores de configuración, grupos de subprocesos, cachés.
1.2. Factory Method (Método de fábrica)
Define una interfaz para crear un objeto, pero permite que las subclases alteren el tipo de objeto creado.
// Producto abstractointerface Notification { fun send(message: String)}
// Productos concretosclass EmailNotification : Notification { override fun send(message: String) = println("Email: $message")}
class SMSNotification : Notification { override fun send(message: String) = println("SMS: $message")}
// Fábricaabstract class NotificationFactory { abstract fun createNotification(): Notification
fun notifyUser(message: String) { val notification = createNotification() notification.send(message) }}
class EmailFactory : NotificationFactory() { override fun createNotification(): Notification = EmailNotification()}
class SMSFactory : NotificationFactory() { override fun createNotification(): Notification = SMSNotification()}
fun main() { val factory: NotificationFactory = EmailFactory() factory.notifyUser("Hello from Kotlin!")}Cuándo usarlo: cuando el tipo de objetos a crear no se conoce de antemano.
1.3. Builder (Constructor)
Permite crear objetos complejos paso a paso. En Kotlin se implementa muy bien mediante apply y argumentos con nombre.
data class Pizza( val size: String, val cheese: Boolean = false, val pepperoni: Boolean = false, val mushrooms: Boolean = false) { // Builder incorporado class Builder(private val size: String) { private var cheese: Boolean = false private var pepperoni: Boolean = false private var mushrooms: Boolean = false
fun cheese(value: Boolean) = apply { cheese = value } fun pepperoni(value: Boolean) = apply { pepperoni = value } fun mushrooms(value: Boolean) = apply { mushrooms = value }
fun build() = Pizza(size, cheese, pepperoni, mushrooms) }}
fun main() { val pizza = Pizza.Builder("Large") .cheese(true) .pepperoni(true) .build() println(pizza)}Cuándo usarlo: cuando hay una gran cantidad de parámetros opcionales.
2. Patrones estructurales (Structural)
Estos patrones definen formas de construir clases y objetos en estructuras más grandes.
2.1. Adapter (Adaptador)
Permite que objetos con interfaces incompatibles trabajen juntos. En Kotlin se usa a menudo mediante extension functions.
// Interfaz antiguainterface OldLogger { fun logMessage(msg: String)}
// Interfaz nuevainterface NewLogger { fun log(level: Stri