Patrones de diseño en Kotlin con ejemplos: nivel avanzado
\n\nLos patrones de diseño son soluciones probadas para problemas típicos en el desarrollo de software. Si ya dominas los patrones básicos (Singleton, Factory, Observer), es hora de pasar al siguiente nivel. En este artículo, analizaremos cuatro patrones avanzados — Cadena de responsabilidad, Intérprete, Mediador y Memento — y mostraremos su implementación en Kotlin teniendo en cuenta los modismos del lenguaje (sealed class, extension functions, lambdas).
\n\n1. Cadena de responsabilidad (Chain of Responsibility)
\n\nPropósito: Permite pasar una solicitud secuencialmente a lo largo de una cadena de manejadores hasta que uno de ellos la procese.
\n\nProblema: Un sistema de registro donde los mensajes de diferentes niveles (DEBUG, INFO, ERROR) deben ser procesados por diferentes registradores.
\n\nImplementación en Kotlin:
\n\n// Manejador abstracto\nabstract class Logger(private val level: Int) {\n private var nextLogger: Logger? = null\n\n fun setNext(logger: Logger): Logger {\n this.nextLogger = logger\n return logger\n }\n\n fun log(level: Int, message: String) {\n if (this.level <= level) {\n write(message)\n }\n nextLogger?.log(level, message)\n }\n\n protected abstract fun write(message: String)\n}\n\n// Manejadores concretos\nclass ConsoleLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[Consola] $message")\n}\n\nclass FileLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[Archivo] $message")\n}\n\nclass EmailLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[Correo] $message")\n}\n\n// Uso\nfun main() {\n val logger = ConsoleLogger(1).apply {\n setNext(FileLogger(2))\n .setNext(EmailLogger(3))\n }\n logger.log(2, "Advertencia: nivel bajo de memoria")\n // Salida:\n // [Consola] Advertencia: nivel bajo de memoria\n // [Archivo] Advertencia: nivel bajo de memoria\n}\n\nVentajas en Kotlin: Gracias a apply y la llamada en cadena de setNext, el código se ve conciso. También se puede reemplazar la clase abstracta por una interfaz con implementación predeterminada.
2. Intérprete (Interpreter)
\n\nPropósito: Define la gramática de un lenguaje e interpreta las oraciones de ese lenguaje.
\n\nProblema: Es necesario analizar y evaluar expresiones aritméticas simples (por ejemplo, "3 + 5 - 2").
\n\nImplementación en Kotlin con sealed class:
\n\n// Expresión abstracta\nsealed class Expression {\n abstract fun interpret(): Int\n}\n\n// Expresión terminal — número\ndata class Number(val value: Int) : Expression() {\n override fun interpret() = value\n}\n\n// Expresiones no terminales — operaciones\ndata class Add(val left: Expression, val right: Expression) : Expression() {\n override fun interpret() = left.interpret() + right.interpret()\n}\n\ndata class Subtract(val left: Expression, val right: Expression) : Expression() {\n override fun interpret() = left.interpret() - right.interpret()\n}\n\n// Analizador (simplificado)\nfun parse(tokens: List<String>): Expression {\n val stack = mutableListOf<Expression>()\n var i = 0\n while (i < tokens.size) {\n when (tokens[i]) {\n "+" -> {\n val right = Number(tokens[++i].toInt())\n val left = stack.removeLast()\n stack.add(Add(left, right))\n }\n "-" -> {\n val right = Number(tokens[++i].toInt())\n val left = stack.removeLast()\n stack.add(Subtract(left, right))\n }\n else -> stack.add(Number(tokens[i]