Sobes.tech
Middle

¿Qué patrones de diseño se pueden usar en el lenguaje Kotlin?

sobes.tech AI

Respuesta de la IA

En Kotlin, a menudo se aplican los siguientes patrones:

  • Singleton: Se implementa fácilmente mediante object.
    object MiSingleton {
        fun hacerAlgo() {
            // ...
        }
    }
    
  • Factory Method: Se utiliza una función abstracta en la superclase o interfaz, que las subclases implementan para crear objetos.
    interface Producto {
        fun operacion()
    }
    
    class ProductoConcretoA : Producto {
        override fun operacion() {
            // ...
        }
    }
    
    abstract class Creador {
        abstract fun metodoDeFabrica(): Producto
    
        fun algunaOperacion(): Producto {
            val producto = metodoDeFabrica()
            // ...
            return producto
        }
    }
    
    class CreadorConcretoA : Creador() {
        override fun metodoDeFabrica(): Producto {
            return ProductoConcretoA()
        }
    }
    
  • Abstract Factory: Grupo de fábricas para crear familias relacionadas de objetos.
    interface ProductoAbstractoA {
        fun funcionUtilA()
    }
    
    interface ProductoAbstractoB {
        fun funcionUtilB()
        fun otraFuncionUtilB(colaborador: ProductoAbstractoA)
    }
    
    interface FabricaAbstracta {
        fun crearProductoA(): ProductoAbstractoA
        fun crearProductoB(): ProductoAbstractoB
    }
    
    // Implementaciones concretas de productos y fábricas...
    
  • Builder: Facilita la creación de objetos complejos paso a paso.
    class Pizza private constructor(
        val masa: String,
        val salsa: String?,
        val ingredientes: List<String>
    ) {
        data class Builder(
            var masa: String = "delgada",
            var salsa: String? = null,
            val ingredientes: MutableList<String> = mutableListOf()
        ) {
            fun conMasa(masa: String) = apply { this.masa = masa }
            fun conSalsa(salsa: String) = apply { this.salsa = salsa }
            fun agregarIngrediente(ingrediente: String) = apply { this.ingredientes.add(ingrediente) }
            fun construir() = Pizza(masa, salsa, ingredientes)
        }
    }
    
    fun main() {
        val pizza = Pizza.Builder()
            .conMasa("gruesa")
            .conSalsa("tomate")
            .agregarIngrediente("pepperoni")
            .agregarIngrediente("hongos")
            .construir()
        // ... usar pizza
    }
    
  • Adapter: Permite que objetos con interfaces incompatibles trabajen juntos.
    open class Adaptado {
        fun operacionEspecifica() {
            // ...
        }
    }
    
    interface Objetivo {
        fun operacion()
    }
    
    class Adaptador(private val adaptado: Adaptado) : Objetivo {
        override fun operacion() {
            adaptado.operacionEspecifica()
        }
    }
    
  • Decorator: Añade nuevas funciones a objetos existentes sin modificar su estructura. Las extensiones y delegación en Kotlin facilitan mucho esto.
    interface FuenteDeDatos {
        fun leerDatos(): String
        fun escribirDatos(datos: String)
    }
    
    class FuenteDeDatosArchivo(private val nombreArchivo: String) : FuenteDeDatos {
        override fun leerDatos(): String {
            // Leer del archivo
            return ""
        }
    
        override fun escribirDatos(datos: String) {
            // Escribir en el archivo
        }
    }
    
    class DecoradorEncriptacion(private val fuente: FuenteDeDatos) : FuenteDeDatos by fuente {
        override fun leerDatos(): String {
            // Descifrar datos
            val datos = fuente.leerDatos()
            return datos // datos descifrados
        }
    
        override fun escribirDatos(datos: String) {
            // Cifrar datos
            fuente.escribirDatos(datos) // datos cifrados
        }
    }
    
  • Observer: Define la dependencia "uno a muchos". Las corrutinas Flow y las propiedades delegadas de Kotlin pueden usarse para implementarlo.
    // Implementación simple sin Flow
    interface Observador {
        fun actualizar(mensaje: String)
    }
    
    class Sujeto {
        private val observadores = mutableListOf<Observador>()
    
        fun adjuntar(observador: Observador) {
            observadores.add(observador)
        }
    
        fun quitar(observador: Observador) {
            observadores.remove(observador)
        }
    
        fun notificarObservadores(mensaje: String) {
            observadores.forEach { it.actualizar(mensaje) }
        }
    }
    
  • Strategy: Define una familia de algoritmos, encapsula cada uno y los hace intercambiables.
    interface EstrategiaDePago {
        fun pagar(monto: Double)
    }
    
    class PagarConTarjetaDeCredito(private val numeroTarjeta: String) : EstrategiaDePago {
        override fun pagar(monto: Double) {
            println("Pagando $monto usando Tarjeta de Crédito $numeroTarjeta")
        }
    }
    
    class PagarConPayPal(private val email: String) : EstrategiaDePago {
        override fun pagar(monto: Double) {
            println("Pagando $monto usando PayPal $email")
        }
    }
    
    class Orden(private val estrategiaDePago: EstrategiaDePago) {
        fun procesarOrden(monto: Double) {
            // ... procesamiento de orden
            estrategiaDePago.pagar(monto)
        }
    }
    
  • State: Permite que un objeto cambie su comportamiento dependiendo del estado interno. Las clases selladas y las expresiones when simplifican su implementación.
    sealed class Estado {
        object Inactivo : Estado()
        object Cargando : Estado()
        data class Error(val mensaje: String) : Estado()
        data class Exito(val datos: Any) : Estado()
    }
    
    class Contexto {
        var estado: Estado = Estado.Inactivo
    
        fun manejarEvento() {
            when (estado) {
                Estado.Inactivo -> {
                    println("Manejando evento en estado Inactivo")
                    estado = Estado.Cargando
                }
                Estado.Cargando -> {
                    println("Manejando evento en estado Cargando, ignorando.")
                }
                is Estado.Error -> {
                    println("Manejando evento en estado Error: ${estado.mensaje}")
                    // Posible transición a otro estado
                }
                is Estado.Exito -> {
                    println("Manejando evento en estado Exito: ${estado.datos}")
                    // Posible transición a otro estado
                }
            }
        }
    }
    
  • Repository: Abstrae la lógica de acceso a datos desde la lógica de negocio.

Muchos otros patrones, como Facade, Proxy, Command, Iterator, Template Method y Visitor, también son aplicables en Kotlin. Las características del lenguaje, como extensiones, delegación, data classes y clases selladas, a menudo hacen que la implementación de patrones sea más idiomática y concisa.