Injeção de Dependência em Kotlin: um guia simples para iniciantes

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: o que é Injeção de Dependência e para que serve?

\n

Imagine que você está construindo uma casa. Você precisa de tijolos, cimento e ferramentas. Em vez de ir ao depósito buscá-los toda vez, você simplesmente pede a entrega de todos os materiais no canteiro de obras. Dependency Injection (DI) ou Injeção de Dependência é o mesmo princípio, mas para o seu código.

\n

Injeção de Dependência é um padrão de projeto no qual um objeto recebe suas dependências de fora, em vez de criá-las ele mesmo. Isso torna o código mais flexível, testável e fácil de manter. Neste artigo, vamos explorar DI com exemplos em Kotlin — uma linguagem de programação moderna que é excelente para aprender esse padrão.

\n\n

O problema: por que sem DI o código se torna frágil?

\n

Vamos considerar um exemplo típico. Suponha que temos uma classe UserService que envia notificações por email. Sem DI, ela se parece com isso:

\n
class EmailService {\n    fun sendEmail(to: String, message: String) {\n        println("Enviando email para $to: $message")\n    }\n}\n\nclass UserService {\n    private val emailService = EmailService() // Acoplamento rígido!\n\n    fun notifyUser(email: String) {\n        emailService.sendEmail(email, "Olá, usuário!")\n    }\n}
\n

Qual é o problema? UserService está rigidamente acoplado a uma implementação específica de EmailService. Se você quiser:

\n
    \n
  • Substituir EmailService por SmsService;
  • \n
  • Adicionar logging;
  • \n
  • Escrever testes unitários com um objeto mock,
  • \n
\n

— você terá que alterar o código de UserService. Isso viola o princípio Open/Closed (aberto/fechado) e dificulta os testes.

\n\n

Solução: injeção manual de dependências em Kotlin

\n

A maneira mais simples de implementar DI é passar a dependência pelo construtor. Em Kotlin, isso é feito de forma muito concisa:

\n
// 1. Criamos uma interface (abstração)\ninterface NotificationService {\n    fun send(to: String, message: String)\n}\n\n// 2. Implementamos serviços concretos\nclass EmailService : NotificationService {\n    override fun send(to: String, message: String) {\n        println("Email para $to: $message")\n    }\n}\n\nclass SmsService : NotificationService {\n    override fun send(to: String, message: String) {\n        println("SMS para $to: $message")\n    }\n}\n\n// 3. Injetamos a dependência pelo construtor\nclass UserService(private val notificationService: NotificationService) {\n    fun notifyUser(email: String) {\n        notificationService.send(email, "Olá!")\n    }\n}\n\n// 4. Uso\nfun main() {\n    val emailService = EmailService()\n    val userService = UserService(emailService) // Injeção!\n    userService.notifyUser("user@example.com")\n}
\n

Vantagens dessa abordagem:

\n
    \n
  • Flexibilidade: você pode facilmente substituir EmailService por SmsService;
  • \n
  • Testabilidade: nos testes, você pode passar um objeto mock:
  • \n
\n
class MockNotificationService : NotificationService {\n    var lastMessage: String? = null\n    override fun send(to: String, message: String) {\n        lastMessage = message\n    }\n}\n\nfun test() {\n    val mock = MockNotificationService()\n    val userService = UserService(mock)\n    userService.notifyUser("test@test.com")\n    assert(mock.lastMessage == "Olá!")\n}
\n\n

Frameworks DI: Koin e Dagger para Kotlin

\n

Com DI manual, se o projeto for grande, você escreve muito código boilerplate. É aí que os frameworks DI entram em ação. No mundo Kotlin, dois são populares: Koin (simples) e Dagger/Hilt (poderoso).

\n\n

Koin: simplicidade e velocidade

\n

Koin é um framework DI leve, escrito em Kotlin. Ele não requer geração de código e é

Blogs

Book Recommendations