DIP: Grundlagen, Abhängigkeitsumkehrung in der Entwicklung

Autor: IT Sectr Veröffentlicht: 2026-05-12 Lesezeit: 9 Min.

DIP (Dependency Inversion Principle) — das fünfte SOLID-Prinzip, das die Regeln für den Aufbau von Abhängigkeiten zwischen Modulen definiert: Module auf hoher Ebene sollten nicht von Modulen auf niedriger Ebene abhängen, beide sollten von Abstraktionen abhängen. Abstraktionen sollten nicht von Details abhängen — Details sollten von Abstraktionen abhängen. Dieses Prinzip, beschrieben von Robert Martin in Clean Architecture (2017), liegt der lose gekoppelten Architektur zugrunde. Laut diesem Buch beseitigt das Prinzip der Abhängigkeitsumkehrung starre Kopplungen zwischen Anwendungsschichten.

Wichtigste Erkenntnisse

  • DIP — Prinzip der Abhängigkeitsumkehrung, das fünfte in SOLID, über Architekturgrenzen
  • Module auf hoher Ebene sollten keine Module auf niedriger Ebene importieren — nur Abstraktionen
  • DIP ≠ DI: Dependency Inversion ist ein Architekturprinzip, Dependency Injection ist eine Möglichkeit es umzusetzen
  • Abstraktionen gehören zum Modul auf hoher Ebene, Implementierungen zur niedrigen Ebene
  • DIP kehrt die traditionelle Abhängigkeitshierarchie in mehrschichtigen Architekturen um

Was ist DIP (Dependency Inversion Principle)?

DIP (Dependency Inversion Principle) ist das Prinzip der Abhängigkeitsumkehrung, das die traditionelle Sichtweise auf die Richtung von Abhängigkeiten zwischen Modulen umkehrt. Module auf hoher Ebene (Geschäftslogik) sollten nicht direkt von Modulen auf niedriger Ebene (Datenbank, Netzwerk, UI) abhängen. Stattdessen hängen beide Ebenen von Abstraktionen ab, die im Modul auf hoher Ebene definiert sind.

Die formale Formulierung von DIP umfasst zwei Regeln: A — Module auf hoher Ebene sollten nicht von Modulen auf niedriger Ebene abhängen, beide sollten von Abstraktionen abhängen. B — Abstraktionen sollten nicht von Details abhängen, Details sollten von Abstraktionen abhängen. Die zweite Regel folgt aus der ersten: Wenn eine Abstraktion von Details abhängt, kann sie keine stabile Grundlage für ein Modul auf hoher Ebene sein.

Ohne DIP sieht eine typische Architektur so aus: BusinessLogic → DatabaseRepository — die Geschäftslogik hängt direkt von einem konkreten Repository ab. Mit DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic weiß nichts von der Existenz von DatabaseRepository, es kennt nur das DatabaseService-Interface, das außerhalb der Geschäftslogik implementiert wird.

Abhängigkeitsrichtung in DIP

Umkehrung bedeutet, dass der Kontrollfluss und der Abhängigkeitsfluss in entgegengesetzte Richtungen verlaufen. Der Kontrollfluss verläuft von oben nach unten: UI → ViewModel → UseCase → Repository. Der Abhängigkeitsfluss verläuft von unten nach oben: Repository implementiert ein in UseCase definiertes Interface. Repository (niedrige Ebene) hängt von UseCase (hohe Ebene) ab.

Diese Umkehrung ist der Hauptunterschied zwischen DIP und der gewöhnlichen Schichtentrennung. In der traditionellen Schichtenarchitektur hängt jede Schicht von der darunterliegenden Schicht ab. In der Architektur mit DIP hängen alle Schichten von Abstraktionen ab, während die Implementierung dieser Abstraktionen in der Infrastrukturschicht liegt, die über DI-Mechanismen an die oberen Schichten “angeschlossen” wird.

Wie funktioniert das Prinzip der Abhängigkeitsumkehrung

Der DIP-Mechanismus wird implementiert, indem Abstraktionen in Modulen auf hoher Ebene definiert und in Modulen auf niedriger Ebene realisiert werden. Das Modul auf hoher Ebene deklariert ein Interface für die benötigte Funktionalität. Das Modul auf niedriger Ebene implementiert dieses Interface. Die Verdrahtung (Wiring) erfolgt an der Kompositionswurzel der Anwendung.

Der Prozess der Einführung von DIP in vorhandenen Code: Ein Interface für das Modul auf niedriger Ebene extrahieren, dieses Interface in das Modul auf hoher Ebene (oder in eine separate Abstraktionsschicht) verschieben, die Abhängigkeit des oberen Moduls umschreiben, um das Interface zu verwenden, das Modul auf niedriger Ebene dieses Interface implementieren lassen. Nach diesen Schritten hat sich die Abhängigkeitsrichtung umgekehrt.

DIP erfordert einen Kompositionswurzel-Mechanismus — einen Punkt in der Anwendung, an dem alle Abhängigkeiten erstellt und miteinander verbunden werden. In Android ist dies Application.get() oder die Hilt-Komponente, in iOS — AppDelegate oder SceneDelegate. Die Kompositionswurzel ist der einzige Ort, an dem der Code konkrete Implementierungen kennt.

Schichtenisolierung durch DIP

DIP schafft Architekturgrenzen zwischen den Anwendungsschichten. Wenn ViewModel vom UserRepository-Interface abhängt, entsteht eine Grenze zwischen der Präsentations- und der Domänenschicht: ViewModel (Präsentation) weiß nicht, woher die Daten kommen. Diese Grenze ermöglicht es, die Implementierung von UserRepository (Room → REST → Mock) zu ändern, ohne ViewModel zu beeinträchtigen. Je mehr solcher Grenzen, desto widerstandsfähiger ist die Anwendung gegenüber Änderungen von Frameworks und Bibliotheken.

In der von Google empfohlenen Android-Architektur wird DIP durch UseCases implementiert, die sich in der Domänenschicht befinden und von Repository-Interfaces abhängen. RepositoryImpl befinden sich in der Datenschicht und implementieren diese Interfaces. Die Präsentationsschicht (ViewModel) hängt von UseCases ab. Die Abhängigkeitsrichtung verläuft von der Präsentation zur Domäne, von der Domäne zu den Daten — aber keine Schicht kennt die konkreten Implementierungen einer anderen Schicht.

Unterschied zwischen DIP und DI (Dependency Injection)

DIP und DI werden oft verwechselt, sind aber unterschiedliche Konzepte. DIP ist ein Architekturprinzip (WAS zu tun ist: von Abstraktionen abhängen). DI ist ein Implementierungsmuster (WIE man es tut: Abhängigkeiten über den Konstruktor übergeben). DIP beantwortet die Frage “Worauf sollten Module basieren?”, DI beantwortet “Wie erhalten Objekte ihre Abhängigkeiten?”.

Dependency Injection ist eine Möglichkeit, Abhängigkeiten über Konstruktor, Methode oder Eigenschaft in ein Objekt zu injizieren. Wenn eine Kotlin-Klasse ein Repository-Interface über ihren Konstruktor erhält — das ist DI. Die Tatsache, dass eine ViewModel-Klasse vom Repository-Interface und nicht von einer konkreten RoomRepository-Implementierung abhängt — das ist DIP. DI ist das Werkzeug, DIP ist das Ziel.

Man kann DIP ohne DI-Framework befolgen: manuelles Verdrahten von Abhängigkeiten in der Kompositionswurzel ist ebenfalls DI (manuelles DI). Man kann ein DI-Framework (Dagger, Hilt, Koin) verwenden und gegen DIP verstoßen: Wenn ViewModel direkt ein Repository-Objekt über new() erstellt — ist DIP verletzt, selbst wenn das Framework installiert ist. DIP ist eine Architekturentscheidung, DI ist ein technisches Detail.

DIP-Beispiele in der mobilen Entwicklung

Betrachten wir ein Android-Beispiel zur Anwendung von DIP auf die Datenschicht. Ohne DIP erstellt eine ViewModel direkt eine RoomDatabase und DAO. Mit DIP — die ViewModel hängt vom UserRepository-Interface ab, und die konkrete RoomUserRepository-Implementierung wird extern bereitgestellt.

kotlin
// Abstraktion gehört zur Domänenschicht (hohe Ebene)
interface UserRepository {
    fun getUser(id: Int): User
}

// Domänenschicht hängt nur von der Abstraktion ab
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Implementierung in der Datenschicht hängt von der Domänenschicht-Abstraktion ab
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

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

Ein iOS-Beispiel mit einem Application Coordinator und einem Navigationsprotokoll:

swift
// Navigationsabstraktion in der Domänenschicht
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel hängt von der Abstraktion ab, nicht von UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

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

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

// Coordinator (UIKit-Schicht) implementiert das Protokoll der Domänenschicht
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit-Navigationscode
    }
    func navigateToLogin() {
        // UIKit-Navigationscode
    }
}

Der entscheidende Punkt: AuthViewModel (Domäne) weiß nichts von der Existenz von AppCoordinator (UIKit). Es kennt nur das AuthNavigation-Protokoll. Wenn UIKit morgen durch SwiftUI ersetzt wird — AuthViewModel benötigt keine Änderungen. DIP macht die Domänenschicht unabhängig von UI-Frameworks und -Bibliotheken.

Werkzeuge für DIP: Dagger, Hilt, Koin

Hilt ist das Standard-DI-Werkzeug für Android, empfohlen von Google. Es ist in Jetpack integriert und unterstützt ViewModel, Fragment, Service und andere Android-Komponenten. Hilt automatisiert die Erstellung der Kompositionswurzel durch Annotationen @Module, @Provides, @Inject. Die Verwendung von Hilt garantiert keine DIP-Einhaltung — das UserRepository-Interface muss in der Domänenschicht definiert werden, nicht in der Datenschicht.

Koin ist ein leichtgewichtiges DI-Framework für Kotlin ohne Codegenerierung oder Annotation Processing. Das DSL von Koin (module, single, factory) ist leichter zu erlernen, aber die Abhängigkeitsprüfung erfolgt zur Laufzeit und nicht zur Compile-Zeit. Koin ist aufgrund der iOS-Unterstützung in Multiplattform-Projekten (KMP) beliebt.

Dagger 2 ist der Vorgänger von Hilt und wird immer noch in großen Projekten verwendet. Dagger generiert DI-Code zur Compile-Zeit, was maximale Leistung und Fehlerdiagnose zur Build-Zeit bietet. Hilt ist auf Dagger aufgebaut und bietet eine vereinfachte API. Für neue Projekte empfiehlt Google Hilt als primäres DI-Framework.

Organisation von DI-Modulen nach Schichten

DI-Module sollten den Architekturschichten entsprechen und in DomainModule, DataModule, PresentationModule unterteilt werden. DomainModule stellt nur Abstraktionen und UseCases bereit. DataModule stellt Implementierungen für Abstraktionen bereit. PresentationModule verbindet ViewModels mit UseCases. Diese Organisation stellt sicher, dass die Domänenschicht unabhängig von Infrastrukturbibliotheken bleibt.

Bei der Migration zwischen DI-Frameworks (z.B. von Koin zu Hilt) ändert sich die Struktur des DomainModule nicht — nur die Verdrahtungsmethoden in DataModule und PresentationModule ändern sich. DIP gewährleistet die Isolierung der Domänenlogik, während das DI-Framework ein technischer Verdrahtungsmechanismus ist.

Häufig gestellte Fragen

Sollte DIP immer angewendet werden?

DIP ist an Architekturgrenzen notwendig — zwischen Anwendungsschichten (Domäne → Daten, Präsentation → Domäne). Innerhalb einer einzelnen Schicht kann DIP übertrieben sein. Beispielsweise benötigt eine Hilfsklasse StringFormatter innerhalb der Domänenschicht kein Interface — wenn es keinen Grund gibt, sie zu ersetzen.

Ist DIP dasselbe wie Dependency Injection?

Nein. DIP ist ein Prinzip: Module sollten von Abstraktionen abhängen. DI ist ein Muster: Ein Objekt erhält Abhängigkeiten von außen, anstatt sie selbst zu erstellen. DI ist eine Möglichkeit, DIP zu implementieren, aber DIP kann auch ohne DI befolgt werden (durch Fabriken oder Service Locator). DI ohne DIP ist möglich, hat aber keinen architektonischen Wert.

Wo sollten Interfaces für DIP definiert werden?

Interfaces gehören zu dem Modul, das sie verwendet, nicht zu dem Modul, das sie implementiert. UserRepository wird in der Domänenschicht deklariert und in der Datenschicht implementiert. Dies ist die Schlüsselregel von DIP: Der Eigentümer der Abstraktion ist der Verbraucher, nicht der Anbieter der Implementierung.

Wie wirkt sich DIP auf das Testen aus?

DIP macht Tests auf isolierten Schichten möglich. Eine ViewModel, die von UserRepository (Interface) abhängt, kann mit einer Mock-Implementierung ohne Datenbank getestet werden. Ohne DIP würde die ViewModel von RoomUserRepository abhängen und für jeden Test eine Datenbankeinrichtung erfordern. DIP + DI bieten eine vollständige Modulisolierung während des Testens.

Welches DI-Framework sollte ich für Android wählen?

Hilt ist die Standardwahl für Android-Projekte, empfohlen von Google. Koin ist eine Alternative für Kotlin-Multiplattform-Projekte. Dagger 2 ist für bestehende Projekte, bei denen eine Migration zu Hilt nicht gerechtfertigt ist. Die Wahl des Frameworks hebt die Notwendigkeit nicht auf, DIP auf Architekturebene zu befolgen.

Zusammenfassung

  • DIP (Dependency Inversion Principle) — das fünfte SOLID-Prinzip über Architekturgrenzen durch Abstraktionen
  • Module auf hoher Ebene hängen nicht von Modulen auf niedriger Ebene ab — beide hängen von Abstraktionen ab
  • DIP ≠ DI: Prinzip vs Implementierungsmuster; DI ist das Werkzeug, DIP ist das Ziel
  • Abstraktionen gehören dem Verbraucher (Domänenschicht), nicht dem Anbieter (Datenschicht)
  • Kompositionswurzel — der einzige Ort in der Anwendung, an dem konkrete Abhängigkeiten zusammengesetzt werden
  • Hilt, Koin, Dagger — DI-Werkzeuge, die die Verdrahtung automatisieren, aber die architektonische Entscheidung von DIP nicht ersetzen
  • Die Domänenschicht, nach DIP aufgebaut, bleibt unabhängig von Frameworks, UI und Infrastrukturbibliotheken

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch