Introducción a los patrones de diseño en Kotlin
Los patrones de diseño (design patterns) son soluciones probadas para problemas típicos de desarrollo. En el lenguaje Kotlin, muchos patrones clásicos se implementan de manera más concisa y segura gracias a las capacidades integradas: null-safety, funciones de extensión, corrutinas y un potente sistema de tipos.
En este artículo, analizaremos tres grupos de patrones: creacionales, estructurales y de comportamiento, con ejemplos reales en Kotlin. Aprenderá cómo utilizarlos en el desarrollo diario para crear una arquitectura flexible y mantenible.
Patrones creacionales (Creational)
1. Singleton (Singleton)
En Kotlin, el singleton se implementa con la palabra clave object. Es un singleton perezoso y seguro para subprocesos sin código adicional.
object DatabaseConnection { private val url = "jdbc:postgresql://localhost:5432/mydb" private val user = "admin" private val password = "secret"
fun connect(): String { return "Connected to $url as $user" }}
fun main() { println(DatabaseConnection.connect()) // Verificamos que es el mismo objeto val ref1 = DatabaseConnection val ref2 = DatabaseConnection println(ref1 === ref2) // true}Ventaja: no es necesario escribir synchronized o volatile — Kotlin garantiza una única instancia.
2. Factory Method (Método de fábrica)
Permite delegar la creación de objetos a subclases. En Kotlin, es conveniente usar sealed class y companion object.
sealed class Notification { data class Email(val to: String, val subject: String) : Notification() data class SMS(val phone: String, val message: String) : Notification() data class Push(val deviceId: String, val title: String) : Notification()
companion object Factory { fun createEmail(to: String, subject: String): Notification = Email(to, subject) fun createSMS(phone: String, message: String): Notification = SMS(phone, message) fun createPush(deviceId: String, title: String): Notification = Push(deviceId, title) }}
fun send(notification: Notification) { when (notification) { is Notification.Email -> println("Email to ${notification.to}") is Notification.SMS -> println("SMS to ${notification.phone}") is Notification.Push -> println("Push to ${notification.deviceId}") }}
fun main() { val email = Notification.createEmail("user@example.com", "Hello") send(email)}3. Builder (Constructor)
En Kotlin, el Builder se puede reemplazar con argumentos con nombre y valores predeterminados. Pero para objetos complejos con validación, el Builder clásico sigue siendo relevante.
class Pizza private constructor( val size: String, val cheese: Boolean, val pepperoni: Boolean, val mushrooms: Boolean) { class Builder { private var size: String = "Medium" private var cheese: Boolean = false private var pepperoni: Boolean = false private var mushrooms: Boolean = false
fun size(size: String) = apply { this.size = size } fun cheese() = apply { this.cheese = true } fun pepperoni() = apply { this.pepperoni = true } fun mushrooms() = apply { this.mushrooms = true }
fun build(): Pizza { require(size in listOf("Small", "Medium", "Large")) { "Invalid size" } return Pizza(size, cheese, pepperoni, mushrooms) } }}
fun main() { val pizza = Pizza.Builder() .size("Large") .cheese() .pepperoni() .build() println("Pizza: ${pizza.size}, cheese=${pizza.cheese}, pepperoni=${pizza.pepperoni}")}Patrones estructurales (Structural)
4. Adapter (Adaptador)
El adaptador permite que objetos con