Introdução: por que SOLID é importante em Kotlin?
SOLID é a sigla de cinco princípios fundamentais da programação orientada a objetos, formulados por Robert Martin (Tio Bob). No ecossistema Kotlin, que combina as melhores características da programação orientada a objetos e funcional, a observância dos princípios SOLID ajuda a criar código flexível, testável e de fácil manutenção. Esses princípios são especialmente relevantes para desenvolvimento Android, aplicações server-side com Ktor e sistemas multithread baseados em corrotinas.
Neste artigo, abordaremos cada um dos cinco princípios SOLID com exemplos adaptados aos idiomas do Kotlin. Você aprenderá como evitar erros comuns e tornar seu código não apenas funcional, mas verdadeiramente de qualidade.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Essência do princípio
Uma classe deve ter apenas um motivo para mudar. Em outras palavras, cada classe é responsável por exatamente uma funcionalidade.
Exemplo ruim (violação do SRP)
class UserManager { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ } fun sendEmail(to: String, subject: String) { /* ... */ } fun generateReport(userId: Int): String { /* ... */ }}Aqui, UserManager é responsável pelo gerenciamento de usuários, envio de e-mails e geração de relatórios. Uma alteração na lógica de e-mail implicaria modificar esta classe, embora ela não devesse lidar com correspondência.
Exemplo bom (cumprimento do SRP)
class UserRepository { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ }}
class EmailService { fun sendEmail(to: String, subject: String) { /* ... */ }}
class ReportGenerator { fun generateReport(userId: Int): String { /* ... */ }}Agora cada classe tem um único motivo para mudar. Isso simplifica os testes e a manutenção do código.
2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
Essência do princípio
Entidades de software (classes, módulos, funções) devem estar abertas para extensão, mas fechadas para modificação. Isso significa que novos comportamentos são adicionados por meio de herança ou composição, e não alterando o código existente.
Exemplo ruim (violação do OCP)
class PaymentProcessor { fun process(paymentType: String) { when (paymentType) { "credit_card" -> // processar cartão de crédito "paypal" -> // processar PayPal else -> throw IllegalArgumentException() } }}Ao adicionar um novo método de pagamento (por exemplo, Apple Pay), seria necessário modificar PaymentProcessor, violando o OCP.
Exemplo bom (cumprimento do OCP)
interface PaymentMethod { fun process(amount: Double)}
class CreditCardPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PayPalPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PaymentProcessor(private val method: PaymentMethod) { fun processPayment(amount: Double) { method.process(amount) }}Agora, para adicionar um novo método de pagamento, basta criar uma classe que implemente PaymentMethod, sem tocar no código existente.
3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Barbara Liskov
Essência do princípio
Objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa. Simplificando, se você tem uma classe Bird e uma subclasse Penguin, o código que funciona com Bird não deve quebrar ao substituí-lo por Penguin.
Exemplo ruim (violação do LSP)
open class Rectangle { open var width: Int = 0