Princípios SOLID em Kotlin: da teoria à prática
\n\nSOLID é um acrônimo de cinco princípios fundamentais da programação orientada a objetos, formulados por Robert Martin. Esses princípios ajudam a criar código flexível, sustentável e escalável. Neste artigo, vamos detalhar cada princípio SOLID com exemplos do desenvolvimento real em Kotlin.
\n\nCompreender SOLID é criticamente importante para qualquer desenvolvedor Kotlin, especialmente ao trabalhar com frameworks como Spring Boot ou Ktor. Esses princípios estão na base da arquitetura limpa e dos padrões de projeto.
\n\n1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
\n\nEssência: Cada classe deve ter apenas um motivo para mudar. Em outras palavras, a classe deve ser responsável por uma única funcionalidade.
\n\nEm Kotlin, esse princípio é especialmente importante devido ao suporte ao estilo funcional. Vejamos um exemplo ruim:
\n\n// ❌ Violação do SRP: a classe lida com logging e trabalho com BD\nclass UserService {\n fun saveUser(user: User) {\n // Logging\n println("Saving user: ${user.name}")\n // Lógica de negócio\n validateUser(user)\n // Trabalho com BD\n database.insert(user)\n }\n}\n\n\nVamos corrigir a situação dividindo a responsabilidade:
\n\n// ✅ Abordagem correta: cada responsabilidade em uma classe separada\nclass Logger {\n fun log(message: String) = println("[LOG] $message")\n}\n\nclass UserValidator {\n fun validate(user: User): Boolean = user.name.isNotBlank()\n}\n\nclass UserRepository {\n fun save(user: User) { /* salvar no BD */ }\n}\n\nclass UserService(\n private val logger: Logger,\n private val validator: UserValidator,\n private val repository: UserRepository\n) {\n fun saveUser(user: User) {\n logger.log("Saving user: ${user.name}")\n if (validator.validate(user)) {\n repository.save(user)\n }\n }\n}\n\n\n2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
\n\nEssência: As classes devem estar abertas para extensão, mas fechadas para modificação. Novas funcionalidades devem ser adicionadas por meio de herança ou composição, sem alterar o código existente.
\n\nEm Kotlin, sealed classes e interfaces são ótimas para implementar o OCP:
\n\n// ❌ Violação do OCP: ao adicionar um novo tipo de figura, é preciso alterar a classe\nclass AreaCalculator {\n fun calculate(shape: Any): Double = when (shape) {\n is Circle -> 3.14 * shape.radius * shape.radius\n is Rectangle -> shape.width * shape.height\n else -> throw IllegalArgumentException()\n }\n}\n\n\nImplementação correta usando polimorfismo:
\n\n// ✅ Abordagem correta: novas figuras são adicionadas sem alterar o código existente\ninterface Shape {\n fun area(): Double\n}\n\nclass Circle(val radius: Double) : Shape {\n override fun area() = Math.PI * radius * radius\n}\n\nclass Rectangle(val width: Double, val height: Double) : Shape {\n override fun area() = width * height\n}\n\n// Agora é possível adicionar um triângulo sem alterar o AreaCalculator\nclass Triangle(val base: Double, val height: Double) : Shape {\n override fun area() = 0.5 * base * height\n}\n\nclass AreaCalculator {\n fun calculate(shape: Shape) = shape.area()\n}\n\n\n3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Barbara Liskov
\n\nEssência: Objetos de uma classe base devem ser substituíveis por objetos de classes derivadas sem comprometer o funcionamento do programa. A subclasse não deve enfraquecer as pré-condições nem fortalecer as pós-condições.
\n\nVejamos o exemplo clássico com retângulo e quadrado:
\n\n// ❌ Violação do LSP: o quadrado não pode substituir o retângulo\nopen class Rectangle(\n open var width: Int,\n open var height: Int\n) {\n fun area() = width * height\n}\n\nclass Square(sid