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.