DIP: basisprincipes, inversie van afhankelijkheden in ontwikkeling

Auteur: IT Sectr Gepubliceerd: 2026-05-12 Leestijd: 9 min

DIP (Dependency Inversion Principle) — het vijfde SOLID-principe dat de regels bepaalt voor het opbouwen van afhankelijkheden tussen modules: modules van een hoger niveau mogen niet afhankelijk zijn van modules van een lager niveau, beide moeten afhankelijk zijn van abstracties. Abstracties mogen niet afhankelijk zijn van details — details moeten afhankelijk zijn van abstracties. Dit principe, beschreven door Robert Martin in Clean Architecture (2017), vormt de basis van losjes gekoppelde architectuur. Volgens dit boek elimineert het principe van inversie van afhankelijkheden starre verbindingen tussen lagen van de applicatie.

Belangrijkste

  • DIP — principe van inversie van afhankelijkheden, het vijfde in SOLID, over architecturale grenzen
  • Modules van hoger niveau mogen geen modules van lager niveau importeren — alleen abstracties
  • DIP ≠ DI: Dependency Inversion — architecturaal principe, Dependency Injection — manier van implementatie
  • Abstracties behoren tot de module van hoger niveau, implementaties tot die van lager niveau
  • DIP keert de traditionele hiërarchie van afhankelijkheden om in gelaagde architecturen

Wat is DIP (Dependency Inversion Principle)?

DIP (Dependency Inversion Principle) — het principe van inversie van afhankelijkheden, dat de traditionele opvatting over de richting van afhankelijkheden tussen modules omkeert. Modules van hoog niveau (bedrijfslogica) mogen niet direct afhankelijk zijn van modules van laag niveau (database, netwerk, UI). In plaats daarvan zijn beide niveaus afhankelijk van abstracties die worden gedefinieerd in de module van hoger niveau.

De formele formulering van DIP omvat twee regels: A — modules van hoger niveau mogen niet afhankelijk zijn van modules van lager niveau, beide moeten afhankelijk zijn van abstracties. B — abstracties mogen niet afhankelijk zijn van details, details moeten afhankelijk zijn van abstracties. De tweede regel is een gevolg van de eerste: als abstractie afhankelijk is van details, kan het geen stabiele basis zijn voor de module van hoger niveau.

Zonder DIP ziet een typische architectuur er zo uit: BusinessLogic → DatabaseRepository — bedrijfslogica is direct afhankelijk van een concrete repository. Met DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic weet niet van het bestaan van DatabaseRepository, het kent alleen de DatabaseService-interface, die buiten de bedrijfslogica wordt geïmplementeerd.

Richting van afhankelijkheden in DIP

Inversie betekent dat de controlestroom en de afhankelijkheidsstroom in tegenovergestelde richtingen gaan. De controlestroom gaat van boven naar beneden: UI → ViewModel → UseCase → Repository. De afhankelijkheidsstroom gaat van beneden naar boven: Repository implementeert de interface gedefinieerd in UseCase. Repository (lager niveau) is afhankelijk van UseCase (hoger niveau).

Deze inversie is het belangrijkste verschil tussen DIP en een gewone verdeling in lagen. In een traditionele gelaagde architectuur is elke laag afhankelijk van de laag eronder. In een architectuur met DIP zijn alle lagen afhankelijk van abstracties, en de implementatie van deze abstracties bevindt zich in de infrastructuurlaag, die via DI-mechanismen wordt aangesloten op de bovenste lagen.

Hoe werkt het principe van inversie van afhankelijkheden

Het DIP-mechanisme wordt gerealiseerd door abstracties te definiëren in modules van hoger niveau en deze te implementeren in modules van lager niveau. De module van hoger niveau declareert een interface voor de benodigde functionaliteit. De module van lager niveau implementeert deze interface. Het samenstellen (wiring) gebeurt op het niveau van de compositiewortel van de applicatie.

Het proces van het toepassen van DIP in bestaande code: isoleer de interface voor de module van laag niveau, verplaats deze interface naar de module van hoger niveau (of naar een aparte abstractielaag), herschrijf de afhankelijkheid van de bovenste module naar de interface, dwing de module van laag niveau om deze interface te implementeren. Na deze stappen is de richting van de afhankelijkheid omgekeerd.

DIP vereist een mechanisme van een compositiewortel — het punt in de applicatie waar alle afhankelijkheden worden gemaakt en aan elkaar worden gekoppeld. In Android is dit Application.get() of de Hilt-component, in iOS — AppDelegate of SceneDelegate. De compositiewortel is de enige plaats waar de code op de hoogte is van concrete implementaties.

Isolatie van lagen door DIP

DIP vormt architecturale grenzen tussen de lagen van de applicatie. Wanneer ViewModel afhankelijk is van de UserRepository-interface, ontstaat er een grens tussen de presentation- en domain-laag: ViewModel (presentation) weet niet waar de gegevens vandaan komen. Deze grens maakt het mogelijk om de implementatie van UserRepository te wijzigen (Room → REST → Mock) zonder ViewModel te beïnvloeden. Hoe meer van dergelijke grenzen, hoe beter de applicatie bestand is tegen veranderingen van frameworks en bibliotheken.

In de door Google aanbevolen Android-architectuur wordt DIP geïmplementeerd via UseCase, die zich in de domain-laag bevinden en afhankelijk zijn van Repository-interfaces. RepositoryImpl bevinden zich in de data-laag en implementeren deze interfaces. De presentation-laag (ViewModel) is afhankelijk van UseCase. De richting van afhankelijkheden gaat van presentation naar domain, van domain naar data — maar geen enkele laag weet van de concrete implementaties van een andere laag.

Verschil tussen DIP en DI (Dependency Injection)

DIP en DI worden vaak verward, maar het zijn verschillende concepten. DIP — architecturaal principe (WAT moet er gedaan worden: afhankelijk zijn van abstracties). DI — implementatiepatroon (HOE dit te doen: afhankelijkheden doorgeven via de constructor). DIP beantwoordt de vraag waarop moeten modules gebaseerd zijn?, DI — hoe krijgen objecten hun afhankelijkheden?.

Dependency Injection — een manier om afhankelijkheden in een object te injecteren via constructor, methode of eigenschap. Wanneer in een Kotlin-klasse de Repository-interface via de constructor wordt doorgegeven — is dit DI. En het feit dat de ViewModel-klasse afhankelijk is van de Repository-interface, niet van de concrete RoomRepository-implementatie — is dit DIP. DI — tool, DIP — doel.

Men kan DIP naleven zonder DI-framework: handmatig koppelen van afhankelijkheden in de compositiewortel is ook DI (manual DI). Men kan een DI-framework (Dagger, Hilt, Koin) gebruiken terwijl DIP wordt geschonden: als ViewModel direct een Repository-object aanmaakt via new() — is DIP geschonden, zelfs als het framework is geïnstalleerd. DIP — architecturale beslissing, DI — technisch detail.

Voorbeelden van DIP in mobiele ontwikkeling

Laten we het Android-voorbeeld bekijken van het toepassen van DIP op de data-laag. Zonder DIP maakt ViewModel direct RoomDatabase en DAO aan. Met DIP — is ViewModel afhankelijk van de UserRepository-interface, en wordt de concrete RoomUserRepository-implementatie van buitenaf geleverd.

kotlin
// Abstractie behoort tot de domain-laag (hoger niveau)
interface UserRepository {
    fun getUser(id: Int): User
}

// Domain-laag is alleen afhankelijk van abstractie
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Implementatie in de data-laag is afhankelijk van de abstractie van de domain-laag
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Compositiewortel
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

iOS-voorbeeld met Application Coordinator en protocol voor navigatie:

swift
// Navigatie-abstractie in de domain-laag
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel is afhankelijk van abstractie, niet van UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

    init(navigation: AuthNavigation) {
        self.navigation = navigation
    }

    func onLoginSuccess() {
        navigation.navigateToHome()
    }
}

// Coordinator (UIKit-laag) implementeert het protocol van de domain-laag
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit-navigatiecode
    }
    func navigateToLogin() {
        // UIKit-navigatiecode
    }
}

Belangrijk punt: AuthViewModel (domain) weet niet van het bestaan van AppCoordinator (UIKit). Het kent alleen het AuthNavigation-protocol. Als morgen UIKit wordt vervangen door SwiftUI — vereist AuthViewModel geen wijzigingen. DIP maakt de domain-laag onafhankelijk van frameworks en UI-bibliotheken.

Tools voor DIP: Dagger, Hilt, Koin

Hilt — het standaard DI-gereedschap voor Android, aanbevolen door Google. Geïntegreerd in Jetpack, ondersteunt ViewModel, Fragment, Service en andere Android-componenten. Hilt automatiseert het maken van de compositiewortel via annotaties @Module, @Provides, @Inject. Het gebruik van Hilt garandeert niet het naleven van DIP — de UserRepository-interface moet worden gedefinieerd in de domain-laag, niet in de data-laag.

Koin — een lichtgewicht DI-framework voor Kotlin zonder codegeneratie en annotatieprocessor. De Koin DSL (module, single, factory) is makkelijker te leren, maar de controle van afhankelijkheden vindt plaats in runtime, niet tijdens compilatie. Koin is populair in multiplatform-projecten (KMP) vanwege de ondersteuning voor iOS.

Dagger 2 — de voorganger van Hilt, nog steeds gebruikt in grote projecten. Dagger genereert DI-code tijdens compilatie, wat maximale prestaties en foutdiagnose tijdens de build-fase oplevert. Hilt is gebouwd op Dagger en biedt een vereenvoudigde API. Voor nieuwe projecten beveelt Google Hilt aan als het primaire DI-framework.

Organisatie van DI-modules per laag

DI-modules moeten overeenkomen met de architecturale lagen en worden verdeeld in DomainModule, DataModule, PresentationModule. DomainModule levert alleen abstracties en UseCase. DataModule levert implementaties voor abstracties. PresentationModule verbindt ViewModel met UseCase. Een dergelijke organisatie garandeert dat de domain-laag onafhankelijk blijft van infrastructurele bibliotheken.

Bij migratie tussen DI-frameworks (bijvoorbeeld van Koin naar Hilt) verandert de structuur van DomainModule niet — alleen de koppelingsmethoden in DataModule en PresentationModule veranderen. DIP zorgt voor isolatie van de domain-logica, en het DI-framework is het technische koppelingsmechanisme.

Veelgestelde vragen

Moet DIP altijd worden toegepast?

DIP is noodzakelijk op architecturale grenzen — tussen de lagen van de applicatie (domain → data, presentation → domain). Binnen een enkele laag kan DIP overbodig zijn. Een hulpklasse zoals StringFormatter in de domain-laag vereist bijvoorbeeld geen interface — tenzij er aanwijzingen zijn voor vervanging.

Is DIP hetzelfde als dependency injection?

Nee. DIP — principe: modules moeten afhankelijk zijn van abstracties. DI — patroon: object ontvangt afhankelijkheden van buitenaf, niet het creëert ze zelf. DI is een manier om DIP te implementeren, maar DIP kan worden nageleefd zonder DI (via fabrieken of service locator). DI zonder DIP is mogelijk, maar heeft geen architecturale waarde.

Waar moeten interfaces voor DIP worden gedefinieerd?

Interfaces behoren tot de module die ze gebruikt, niet tot de module die ze implementeert. UserRepository wordt gedeclareerd in de domain-laag en geïmplementeerd in de data-laag. Dit is de belangrijkste regel van DIP: de eigenaar van de abstractie is de consument, niet de leverancier van de implementatie.

Hoe beïnvloedt DIP het testen?

DIP maakt testen mogelijk op geïsoleerde lagen. ViewModel dat afhankelijk is van UserRepository (interface) wordt getest met een mock-implementatie zonder database. Zonder DIP zou ViewModel afhankelijk zijn van RoomUserRepository en voor elke test databaseconfiguratie vereisen. DIP + DI zorgen voor volledige isolatie van modules bij het testen.

Welk DI-framework kiezen voor Android?

Hilt — de standaardkeuze voor Android-projecten, aanbevolen door Google. Koin — een alternatief voor Kotlin Multiplatform-projecten. Dagger 2 — voor bestaande projecten waar migratie naar Hilt ongerechtvaardigd is. De keuze van het framework heft de noodzaak om DIP op architectuurniveau na te leven niet op.

Samenvatting

  • DIP (Dependency Inversion Principle) — het vijfde SOLID-principe over architecturale grenzen via abstracties
  • Modules van hoger niveau zijn niet afhankelijk van modules van lager niveau — beide zijn afhankelijk van abstracties
  • DIP ≠ DI: principe versus implementatiepatroon; DI — manier, DIP — doel
  • Abstracties behoren tot de consument (domain-laag), niet tot de leverancier (data-laag)
  • Compositiewortel — de enige plaats in de applicatie waar concrete afhankelijkheden worden verzameld
  • Hilt, Koin, Dagger — DI-tools die het koppelen automatiseren, maar de architecturale beslissing DIP niet vervangen
  • Domain-laag gebouwd volgens DIP blijft onafhankelijk van frameworks, UI en infrastructurele bibliotheken

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook