Padrões de Design em Kotlin com Exemplos: Nível Avançado
\n\nPadrões de design são soluções comprovadas para problemas típicos no desenvolvimento de software. Se você já domina os padrões básicos (Singleton, Factory, Observer), é hora de passar para o próximo nível. Neste artigo, vamos explorar quatro padrões avançados — Cadeia de Responsabilidade, Interpretador, Mediador e Snapshot — e mostrar sua implementação em Kotlin, considerando as expressões idiomáticas da linguagem (sealed class, extension functions, lambdas).
\n\n1. Cadeia de Responsabilidade (Chain of Responsibility)
\n\nPropósito: Permite passar uma solicitação sequencialmente por uma cadeia de manipuladores até que um deles a processe.
\n\nProblema: Um sistema de logging onde mensagens de diferentes níveis (DEBUG, INFO, ERROR) devem ser processadas por diferentes loggers.
\n\nImplementação em Kotlin:
\n\n// Manipulador abstrato\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// Manipuladores concretos\nclass ConsoleLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[Console] $message")\n}\n\nclass FileLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[File] $message")\n}\n\nclass EmailLogger(level: Int) : Logger(level) {\n override fun write(message: String) = println("[Email] $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, "Aviso: baixo nível de memória")\n // Saída:\n // [Console] Aviso: baixo nível de memória\n // [File] Aviso: baixo nível de memória\n}\n\nVantagens em Kotlin: Graças ao apply e à chamada encadeada de setNext, o código fica conciso. Também é possível substituir a classe abstrata por uma interface com implementação padrão.
2. Interpretador (Interpreter)
\n\nPropósito: Define uma gramática para uma linguagem e interpreta sentenças dessa linguagem.
\n\nProblema: Necessidade de analisar e calcular expressões aritméticas simples (por exemplo, "3 + 5 - 2").
\n\nImplementação em Kotlin com sealed class:
\n\n// Expressão abstrata\nsealed class Expression {\n abstract fun interpret(): Int\n}\n\n// Expressão terminal — número\ndata class Number(val value: Int) : Expression() {\n override fun interpret() = value\n}\n\n// Expressões não terminais — operações\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// Analisador (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]