DIP (Dependency Inversion Principle) — o quinto princípio SOLID que define as regras para construir dependências entre módulos: módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações. As abstrações não devem depender de detalhes — os detalhes devem depender das abstrações. Este princípio, descrito por Robert Martin em Clean Architecture (2017), está na base da arquitetura fracamente acoplada. De acordo com este livro, o princípio da inversão de dependências elimina os acoplamentos rígidos entre as camadas da aplicação.
Pontos principais
DIP (Dependency Inversion Principle) é o princípio da inversão de dependências que inverte a visão tradicional sobre a direção das dependências entre módulos. Módulos de alto nível (lógica de negócio) não devem depender diretamente de módulos de baixo nível (base de dados, rede, UI). Em vez disso, ambos os níveis dependem de abstrações definidas no módulo de alto nível.
A formulação formal do DIP inclui duas regras: A — módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações. B — abstrações não devem depender de detalhes, detalhes devem depender de abstrações. A segunda regra decorre da primeira: se uma abstração depende de detalhes, ela não pode ser uma base estável para um módulo de alto nível.
Sem DIP, uma arquitetura típica se parece com isto: BusinessLogic → DatabaseRepository — a lógica de negócio depende diretamente de um repositório concreto. Com DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic não sabe da existência de DatabaseRepository, ele conhece apenas a interface DatabaseService, que é implementada fora da lógica de negócio.
Inversão significa que o fluxo de controle e o fluxo de dependências vão em direções opostas. O fluxo de controle vai de cima para baixo: UI → ViewModel → UseCase → Repository. O fluxo de dependências vai de baixo para cima: Repository implementa uma interface definida em UseCase. Repository (baixo nível) depende de UseCase (alto nível).
Essa inversão é a diferença chave entre DIP e a separação comum em camadas. Na arquitetura tradicional em camadas, cada camada depende da camada inferior. Na arquitetura com DIP, todas as camadas dependem de abstrações, enquanto a implementação dessas abstrações reside na camada de infraestrutura, que é “conectada” às camadas superiores através de mecanismos de DI.
O mecanismo do DIP é implementado definindo abstrações nos módulos de alto nível e realizando-as nos módulos de baixo nível. O módulo de alto nível declara uma interface para a funcionalidade de que necessita. O módulo de baixo nível implementa essa interface. A montagem (wiring) ocorre na raiz de composição da aplicação.
O processo de introduzir DIP em código existente: extrair uma interface para o módulo de baixo nível, mover esta interface para o módulo de alto nível (ou para uma camada separada de abstrações), reescrever a dependência do módulo de alto nível para usar a interface, fazer o módulo de baixo nível implementar esta interface. Após estes passos, a direção da dependência foi invertida.
DIP requer um mecanismo de raiz de composição — um ponto na aplicação onde todas as dependências são criadas e ligadas entre si. Em Android é Application.get() ou o componente Hilt, em iOS — AppDelegate ou SceneDelegate. A raiz de composição é o único lugar onde o código conhece as implementações concretas.
DIP cria limites arquiteturais entre as camadas da aplicação. Quando ViewModel depende da interface UserRepository, forma-se um limite entre as camadas de apresentação e domínio: ViewModel (apresentação) não sabe de onde vêm os dados. Este limite permite alterar a implementação de UserRepository (Room → REST → Mock) sem afetar ViewModel. Quanto mais estes limites, mais resiliente é a aplicação a mudanças de frameworks e bibliotecas.
Na arquitetura Android recomendada pelo Google, DIP é implementado através de UseCases que residem na camada de domínio e dependem de interfaces Repository. RepositoryImpl estão na camada de dados e implementam estas interfaces. A camada de apresentação (ViewModel) depende de UseCases. A direção das dependências vai da apresentação para o domínio, do domínio para os dados — mas nenhuma camada conhece as implementações concretas de outra camada.
DIP e DI são frequentemente confundidos, mas são conceitos diferentes. DIP é um princípio arquitetural (O QUE fazer: depender de abstrações). DI é um padrão de implementação (COMO fazer: passar dependências através do construtor). DIP responde à pergunta “em que os módulos devem se basear?”, DI responde a “como os objetos obtêm suas dependências?”.
Dependency Injection é uma forma de injetar dependências num objeto através do construtor, método ou propriedade. Quando uma classe Kotlin recebe uma interface Repository através do seu construtor — isso é DI. O facto de uma classe ViewModel depender da interface Repository e não de uma implementação concreta de RoomRepository — isso é DIP. DI é a ferramenta, DIP é o objetivo.
Pode-se seguir DIP sem um framework DI: a montagem manual de dependências na raiz de composição também é DI (DI manual). Pode-se usar um framework DI (Dagger, Hilt, Koin) violando DIP: se ViewModel cria diretamente um objeto Repository através de new() — DIP é violado, mesmo que o framework esteja instalado. DIP é uma decisão arquitetural, DI é um detalhe técnico.
Vejamos um exemplo em Android de aplicação do DIP à camada de dados. Sem DIP, uma ViewModel cria diretamente uma RoomDatabase e DAO. Com DIP — a ViewModel depende da interface UserRepository, e a implementação concreta RoomUserRepository é fornecida externamente.
// A abstração pertence à camada de domínio (alto nível)
interface UserRepository {
fun getUser(id: Int): User
}
// A camada de domínio depende apenas da abstração
class GetUserUseCase(
private val repo: UserRepository
) {
fun execute(id: Int): User = repo.getUser(id)
}
// A implementação na camada de dados depende da abstração da camada de domínio
class RoomUserRepository(
private val dao: UserDao
) : UserRepository {
override fun getUser(id: Int): User {
return dao.getById(id)
}
}
// Raiz de composição
class AppModule {
fun provideUserRepository(dao: UserDao): UserRepository {
return RoomUserRepository(dao)
}
}
Um exemplo em iOS com um Application Coordinator e um protocolo de navegação:
// Abstração de navegação na camada de domínio
protocol AuthNavigation {
func navigateToHome()
func navigateToLogin()
}
// ViewModel depende da abstração, não do UIKit
final class AuthViewModel {
private let navigation: AuthNavigation
init(navigation: AuthNavigation) {
self.navigation = navigation
}
func onLoginSuccess() {
navigation.navigateToHome()
}
}
// Coordinator (camada UIKit) implementa o protocolo da camada de domínio
final class AppCoordinator: AuthNavigation {
func navigateToHome() {
// Código de navegação UIKit
}
func navigateToLogin() {
// Código de navegação UIKit
}
}
O ponto chave: AuthViewModel (domínio) não sabe da existência de AppCoordinator (UIKit). Ele conhece apenas o protocolo AuthNavigation. Se UIKit for substituído por SwiftUI amanhã — AuthViewModel não requer alterações. DIP torna a camada de domínio independente de frameworks e bibliotecas de UI.
Hilt é a ferramenta DI padrão para Android, recomendada pelo Google. Está integrada no Jetpack, suporta ViewModel, Fragment, Service e outros componentes Android. Hilt automatiza a criação da raiz de composição através das anotações @Module, @Provides, @Inject. Usar Hilt não garante o cumprimento do DIP — a interface UserRepository deve ser definida na camada de domínio, não na camada de dados.
Koin é um framework DI leve para Kotlin sem geração de código ou processamento de anotações. O DSL do Koin (module, single, factory) é mais fácil de aprender, mas a verificação de dependências ocorre em tempo de execução, não em tempo de compilação. Koin é popular em projetos multiplataforma (KMP) graças ao suporte iOS.
Dagger 2 é o predecessor do Hilt, ainda usado em grandes projetos. Dagger gera código DI em tempo de compilação, proporcionando máximo desempenho e diagnóstico de erros em tempo de compilação. Hilt é construído sobre o Dagger e fornece uma API simplificada. Para novos projetos, o Google recomenda Hilt como framework DI principal.
Os módulos DI devem corresponder às camadas arquiteturais e ser separados em DomainModule, DataModule, PresentationModule. DomainModule fornece apenas abstrações e UseCases. DataModule fornece implementações para as abstrações. PresentationModule liga ViewModels a UseCases. Esta organização garante que a camada de domínio permaneça independente das bibliotecas de infraestrutura.
Ao migrar entre frameworks DI (por exemplo, de Koin para Hilt), a estrutura do DomainModule não muda — apenas os métodos de ligação no DataModule e PresentationModule mudam. DIP assegura o isolamento da lógica de domínio, enquanto o framework DI é um mecanismo técnico de ligação.
Perguntas frequentes
DIP é necessário nos limites arquiteturais — entre as camadas da aplicação (domínio → dados, apresentação → domínio). Dentro de uma mesma camada, DIP pode ser excessivo. Por exemplo, uma classe utilitária StringFormatter dentro da camada de domínio não requer uma interface — se não houver razão para substituí-la.
Não. DIP é um princípio: os módulos devem depender de abstrações. DI é um padrão: um objeto recebe dependências do exterior em vez de as criar ele próprio. DI é uma forma de implementar DIP, mas pode-se seguir DIP sem DI (através de fábricas ou service locator). DI sem DIP é possível mas não tem valor arquitetural.
As interfaces pertencem ao módulo que as usa, não ao módulo que as implementa. UserRepository é declarado na camada de domínio e implementado na camada de dados. Esta é a regra chave do DIP: o proprietário da abstração é o consumidor, não o fornecedor da implementação.
DIP torna os testes possíveis em camadas isoladas. Um ViewModel que depende de UserRepository (interface) pode ser testado com uma implementação mock sem base de dados. Sem DIP, o ViewModel dependeria de RoomUserRepository e exigiria configuração da base de dados para cada teste. DIP + DI proporcionam isolamento completo dos módulos durante os testes.
Hilt é a escolha padrão para projetos Android, recomendada pelo Google. Koin é uma alternativa para projetos Kotlin Multiplatform. Dagger 2 é para projetos existentes onde a migração para Hilt não se justifica. Escolher um framework não elimina a necessidade de seguir DIP a nível arquitetural.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também