DIP: основи, инверсия на зависимостите в разработването

Автор: IT Sectr Публикувано: 2026-05-12 Време за четене: 9 мин

DIP (Dependency Inversion Principle) — петият принцип SOLID, който определя правилата за изграждане на зависимости między modułami: модулите от по-високо ниво не трябва да зависят от модули от по-ниско ниво, и двата трябва да зависят от абстракции. Абстракциите не трябва да зависят от детайли — детайлите трябва да зависят от абстракции. Този принцип, описан от Robert Martin в Clean Architecture (2017), е в основата на слабо свързаната архитектура. Według tej książki zasada inwersji zależności eliminuje sztywne powiązania między warstwami aplikacji.

Основни

  • 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

Какво е 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 — модулите от по-високо ниво не трябва да зависят от модули от по-ниско ниво, и двата трябва да зависят от абстракции. 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.

Как работи принципът на инверсия на зависимостите

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.

Разлика между DIP и 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.

Примери за DIP в мобилното разработване

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
// Абстракцията принадлежи на domейния слой (по-високо ниво)
interface UserRepository {
    fun getUser(id: Int): User
}

// Домейният слой зависи само от абстракцията
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Изпълнението в дата слоя зависи от абстракцията на домейния слой
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Композиционен корен
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

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

swift
// Абстракция за навигация в домейния слой
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel зависи от абстракцията, а не от UIKit
final class AuthViewModel {
    private let navigation: AuthNavigation

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

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

// Coordinator (UIKit слой) изпълнява протокола на домейния слой
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit навигационен код
    }
    func navigateToLogin() {
        // UIKit навигационен код
    }
}

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.

Инструменти за 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.

Често задавани въпроси

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.

Обобщение

  • 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

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също