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
// 抽象属于域层(高层)
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自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读