DIP: grunder, beroendeinversion i utveckling

Författare: IT Sectr Publicerad: 2026-05-12 Lästid: 9 min

DIP (Dependency Inversion Principle) — femte principen i SOLID, som definierar reglerna för att bygga beroenden między modułami: moduler på högre nivå ska inte vara beroende av moduler på lägre nivå, båda ska vara beroende av abstraktioner. Abstraktioner ska inte vara beroende av detaljer — detaljer ska vara beroende av abstraktioner. Denna princip, beskriven av Robert Martin i Clean Architecture (2017), ligger till grund för löst kopplad arkitektur. Według tej książki zasada inwersji zależności eliminuje sztywne powiązania między warstwami aplikacji.

Huvudsakligt

  • DIP — zasada inwersji zależności, piąta w SOLID, o granicach architektonicznych
  • Moduły wyższego poziomu nie powinny importować modułów niższego poziomu — tylko abstrakcje
  • DIP ≠ DI: Dependency Inversion — zasada architektoniczna, Dependency Injection — sposób jej implementacji
  • Abstrakcje należą do modułu wyższego poziomu, a implementacje — do niższego
  • DIP odwraca tradycyjną hierarchię zależności w architekturach wielowarstwowych

Vad är DIP (Dependency Inversion Principle)?

DIP (Dependency Inversion Principle) — zasada inwersji zależności, która odwraca tradycyjne pojęcie kierunku zależności między modułami. Moduły wysokiego poziomu (logika biznesowa) nie powinny bezpośrednio zależeć od modułów niskiego poziomu (baza danych, sieć, UI). Zamiast tego oba poziomy zależą od abstrakcji, które są definiowane w module wyższego poziomu.

Formalne sformułowanie DIP zawiera dwie reguły: A — moduler på högre nivå ska inte vara beroende av moduler på lägre nivå, båda ska vara beroende av abstraktioner. B — abstrakcje nie powinny zależeć od szczegółów, szczegóły powinny zależeć od abstrakcji. Druga reguła jest konsekwencją pierwszej: jeśli abstrakcja zależy od szczegółów, nie może być stabilną podstawą dla modułu wyższego poziomu.

Bez DIP typowa architektura wygląda tak: BusinessLogic → DatabaseRepository — logika biznesowa bezpośrednio zależy od konkretnego repozytorium. Z DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic nie wie o istnieniu DatabaseRepository, zna tylko interfejs DatabaseService, który jest implementowany poza logiką biznesową.

Kierunek zależności w DIP

Inwersja oznacza, że przepływ sterowania i przepływ zależności są skierowane w przeciwne strony. Przepływ sterowania idzie z góry na dół: UI → ViewModel → UseCase → Repository. Przepływ zależności idzie z dołu do góry: Repository implementuje interfejs zdefiniowany w UseCase. Repository (niższy poziom) zależy od UseCase (wyższy poziom).

Ta inwersja jest kluczową różnicą między DIP a zwykłym podziałem na warstwy. W tradycyjnej architekturze warstwowej każda warstwa zależy od warstwy poniżej. W architekturze z DIP wszystkie warstwy zależą od abstrakcji, a implementacja tych abstrakcji znajduje się w warstwie infrastruktury, która jest podłączana do górnych warstw przez mechanizmy DI.

Hur fungerar principen om beroendeinversion

Mechanizm DIP jest realizowany poprzez definiowanie abstrakcji w modułach wyższego poziomu i ich implementację w modułach niższego poziomu. Moduł wyższego poziomu deklaruje interfejs dla potrzebnej mu funkcjonalności. Moduł niższego poziomu implementuje ten interfejs. Składanie (wiring) odbywa się na poziomie korzenia kompozycji aplikacji.

Proces wprowadzania DIP do istniejącego kodu: wyodrębnij interfejs dla modułu niskiego poziomu, przenieś ten interfejs do modułu wyższego poziomu (lub do osobnej warstwy abstrakcji), przepisz zależność górnego modułu na interfejs, spraw aby moduł niskiego poziomu implementował ten interfejs. Po tych krokach kierunek zależności zmienił się na przeciwny.

DIP wymaga mechanizmu korzenia kompozycji — punktu w aplikacji, gdzie tworzone są wszystkie zależności i łączone ze sobą. W Androidzie jest to Application.get() lub komponent Hilt, w iOS — AppDelegate lub SceneDelegate. Korzeń kompozycji to jedyne miejsce, gdzie kod zna konkretne implementacje.

Izolacja warstw przez DIP

DIP tworzy granice architektoniczne między warstwami aplikacji. Gdy ViewModel zależy od interfejsu UserRepository, między warstwą presentation a domain powstaje granica: ViewModel (presentation) nie wie, skąd pochodzą dane. Ta granica pozwala zmieniać implementację UserRepository (Room → REST → Mock) bez wpływania na ViewModel. Im więcej takich granic, tym aplikacja jest bardziej odporna na zmiany frameworków i bibliotek.

W architekturze Androida zalecanej przez Google, DIP jest realizowany poprzez UseCase, które znajdują się w warstwie domain i zależą od interfejsów Repository. RepositoryImpl znajdują się w warstwie data i implementują te interfejsy. Warstwa presentation (ViewModel) zależy od UseCase. Kierunek zależności idzie od presentation do domain, od domain do data — ale żadna warstwa nie zna konkretnych implementacji innej warstwy.

Skillnad mellan DIP och DI (Dependency Injection)

DIP i DI są często mylone, ale to różne koncepcje. DIP — zasada architektoniczna (CO należy robić: zależeć od abstrakcji). DI — wzorzec implementacji (JAK to zrobić: przekazywać zależności przez konstruktor). DIP odpowiada na pytanie na czym powinny opierać się moduły?, DI — jak obiekty otrzymują swoje zależności?.

Dependency Injection — sposób wstrzykiwania zależności do obiektu przez konstruktor, metodę lub właściwość. Gdy w klasie Kotlin interfejs Repository jest przekazywany przez konstruktor — to jest DI. A to, że klasa ViewModel zależy od interfejsu Repository, a nie od konkretnej implementacji RoomRepository — to jest DIP. DI — narzędzie, DIP — cel.

Można przestrzegać DIP bez frameworka DI: ręczne wiązanie zależności w korzeniu kompozycji to też DI (manual DI). Można używać frameworka DI (Dagger, Hilt, Koin), naruszając DIP: jeśli ViewModel bezpośrednio tworzy obiekt Repository przez new() — DIP jest naruszony, nawet jeśli framework jest zainstalowany. DIP — decyzja architektoniczna, DI — szczegół techniczny.

Exempel på DIP i mobil utveckling

Rozważmy przykład z Androida zastosowania DIP do warstwy danych. Bez DIP ViewModel bezpośrednio tworzy RoomDatabase i DAO. Z DIP — ViewModel zależy od interfejsu UserRepository, a konkretna implementacja RoomUserRepository jest dostarczana z zewnątrz.

kotlin
// Abstraktionen tillhör domänlagret (högre nivå)
interface UserRepository {
    fun getUser(id: Int): User
}

// Domänlagret är endast beroende av abstraktionen
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Implementeringen i datalagret är beroende av domänlagrets abstraktion
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Sammansättningsrot
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

Przykład z iOS z Application Coordinator i protokołem do nawigacji:

swift
// Navigationsabstraktion i domänlagret
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel är beroende av abstraktion, inte av UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

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

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

// Coordinator (UIKit-lagret) implementerar domänlagrets protokoll
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit-navigationskod
    }
    func navigateToLogin() {
        // UIKit-navigationskod
    }
}

Kluczowy moment: AuthViewModel (domain) nie wie o istnieniu AppCoordinator (UIKit). Zna tylko protokół AuthNavigation. Jeśli jutro UIKit zostanie zastąpiony przez SwiftUI — AuthViewModel nie wymaga zmian. DIP czyni warstwę domain niezależną od frameworków i bibliotek UI.

Verktyg för DIP: Dagger, Hilt, Koin

Hilt — standardowe narzędzie DI dla Androida, zalecane przez Google. Wbudowane w Jetpack, obsługuje ViewModel, Fragment, Service i inne komponenty Androida. Hilt automatyzuje tworzenie korzenia kompozycji przez adnotacje @Module, @Provides, @Inject. Używanie Hilt nie gwarantuje przestrzegania DIP — interfejs UserRepository musi być zdefiniowany w warstwie domain, a nie w warstwie data.

Koin — lekki framework DI dla Kotlin bez generowania kodu i procesora adnotacji. DSL Koina (module, single, factory) jest łatwiejszy do nauki, ale sprawdzanie zależności odbywa się w runtime, a nie na etapie kompilacji. Koin jest popularny w projektach wieloplatformowych (KMP) dzięki obsłudze iOS.

Dagger 2 — poprzednik Hilt, nadal używany w dużych projektach. Dagger generuje kod DI na etapie kompilacji, co daje maksymalną wydajność i diagnostykę błędów na etapie budowania. Hilt jest zbudowany na Dagger i zapewnia uproszczone API. Dla nowych projektów Google zaleca Hilt jako główny framework DI.

Organizacja modułów DI według warstw

Moduły DI powinny odpowiadać warstwom architektonicznym i być podzielone na DomainModule, DataModule, PresentationModule. DomainModule dostarcza tylko abstrakcje i UseCase. DataModule dostarcza implementacje dla abstrakcji. PresentationModule łączy ViewModel z UseCase. Taka organizacja gwarantuje, że warstwa domain pozostaje niezależna od bibliotek infrastrukturalnych.

Podczas migracji między frameworkami DI (np. z Koin na Hilt) struktura DomainModule się nie zmienia — zmieniają się tylko sposoby wiązania w DataModule i PresentationModule. DIP zapewnia izolację logiki domain, a framework DI jest technicznym mechanizmem wiązania.

Vanliga frågor

Czy zawsze trzeba stosować DIP?

DIP jest niezbędny na granicach architektonicznych — między warstwami aplikacji (domain → data, presentation → domain). Wewnątrz jednej warstwy DIP może być zbędny. Na przykład, klasa narzędziowa StringFormatter wewnątrz warstwy domain nie wymaga interfejsu — jeśli nie ma przesłanek do jej zastąpienia.

Czy DIP to to samo co wstrzykiwanie zależności?

Nie. DIP — zasada: moduły powinny zależeć od abstrakcji. DI — wzorzec: obiekt otrzymuje zależności z zewnątrz, a nie tworzy je sam. DI — sposób implementacji DIP, ale DIP można przestrzegać bez DI (przez fabryki lub lokalizator usług). DI bez DIP jest możliwy, ale nie ma wartości architektonicznej.

Gdzie definiować interfejsy dla DIP?

Interfejsy należą do modułu, który ich używa, a nie do tego, który je implementuje. UserRepository jest deklarowany w warstwie domain, a implementowany w warstwie data. To kluczowa zasada DIP: właścicielem abstrakcji jest konsument, a nie dostawca implementacji.

Jak DIP wpływa na testowanie?

DIP umożliwia testowanie na izolowanych warstwach. ViewModel zależny od UserRepository (interfejs) jest testowany z mock-implementacją bez bazy danych. Bez DIP ViewModel zależałby od RoomUserRepository i wymagał konfiguracji bazy danych dla każdego testu. DIP + DI zapewniają pełną izolację modułów podczas testowania.

Jaki framework DI wybrać dla Androida?

Hilt — standardowy wybór dla projektów Android, zalecany przez Google. Koin — alternatywa dla projektów Kotlin Multiplatform. Dagger 2 — dla istniejących projektów, gdzie migracja na Hilt jest nieuzasadniona. Wybór frameworka nie zwalnia z konieczności przestrzegania DIP na poziomie architektury.

Sammanfattning

  • DIP (Dependency Inversion Principle) — piąta zasada SOLID o granicach architektonicznych przez abstrakcje
  • Moduły wyższego poziomu nie zależą od modułów niższego — oba zależą od abstrakcji
  • DIP ≠ DI: zasada kontra wzorzec implementacji; DI — sposób, DIP — cel
  • Abstrakcje należą do konsumenta (warstwa domain), a nie do dostawcy (warstwa data)
  • Korzeń kompozycji — jedyne miejsce w aplikacji, gdzie gromadzone są konkretne zależności
  • Hilt, Koin, Dagger — narzędzia DI automatyzujące wiązanie, ale nie zastępujące decyzji architektonicznej DIP
  • Warstwa domain zbudowana według DIP pozostaje niezależna od frameworków, UI i bibliotek infrastrukturalnych

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också