DIP: основе, инверзија зависности у развоју

Аутор: IT Sectr Објављено: 2026-05-12 Време читања: 9 мин

DIP (Dependency Inversion Principle) — пети принцип SOLID, који одређује правила изградње зависности између модула: модули вишег нивоа не треба да зависе од модула нижег нивоа, оба треба да зависе од апстракција. Апстракције не треба да зависе од детаља — детаљи треба да зависе од апстракција. Овај принцип, који је описао Роберт Мартин у Clean Architecture (2017), лежи у основи слабо повезане архитектуре. Према подацима ове књиге, принцип инверзије зависности уклања чврсте везе између слојева апликације.

Главно

  • DIP — принцип инверзије зависности, пети у SOLID, о архитектонским границама
  • Модули вишег нивоа не треба да увозе модуле нижег нивоа — само апстракције
  • DIP ≠ DI: Dependency Inversion — архитектонски принцип, Dependency Injection — начин његове имплементације
  • Апстракције припадају модулу вишег нивоа, а имплементације — нижег
  • DIP преокреће традиционалну хијерархију зависности у вишеслојним архитектурама

Шта је DIP (Dependency Inversion Principle)?

DIP (Dependency Inversion Principle) — принцип инверзије зависности, који преокреће традиционално схватање о смеру зависности између модула. Високо нивоски модули (пословна логика) не треба директно да зависе од ниско нивоских модула (база података, мрежа, UI). Уместо тога, оба нивоа зависе од апстракција које су дефинисане у модулу вишег нивоа.

Формална формулација DIP укључује два правила: А — модули вишег нивоа не треба да зависе од модула нижег нивоа, оба треба да зависе од апстракција. Б — апстракције не треба да зависе од детаља, детаљи треба да зависе од апстракција. Друго правило је последица првог: ако апстракција зависи од детаља, не може бити стабилна основа за модул вишег нивоа.

Без DIP типична архитектура изгледа овако: BusinessLogic → DatabaseRepository — пословна логика директно зависи од конкретног репозиторијума. Са DIP: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic не зна за постојање DatabaseRepository, зна само интерфејс DatabaseService, који се имплементира изван пословне логике.

Смер зависности у DIP

Инверзија значи да су ток контроле и ток зависности усмерени у супротним смеровима. Ток контроле иде одозго надоле: UI → ViewModel → UseCase → Repository. Ток зависности иде одоздо нагоре: Repository имплементира интерфејс дефинисан у UseCase. Repository (нижи ниво) зависи од UseCase (виши ниво).

Ова инверзија је кључна разлика између DIP и обичне поделе на слојеве. У традиционалној слојевитој архитектури, сваки слој зависи од слоја испод. У архитектури са DIP, сви слојеви зависе од апстракција, а имплементација ових апстракција налази се у инфраструктурном слоју, који се повезује са горњим слојевима путем DI механизама.

Како функционише принцип инверзије зависности

Механизам DIP се реализује кроз дефинисање апстракција у модулима вишег нивоа и њихову имплементацију у модулима нижег нивоа. Модул вишег нивоа декларише интерфејс за потребну функционалност. Модул нижег нивоа имплементира овај интерфејс. Састављање (wiring) се одвија на нивоу композиционог корена апликације.

Процес увођења DIP у постојећи код: издвојити интерфејс за ниско нивоски модул, преместити овај интерфејс у модул вишег нивоа (или у посебан слој апстракција), преписити зависност горњег модула на интерфејс, натерати ниско нивоски модул да имплементира овај интерфејс. Након ових корака, смер зависности се променио у супротан.

DIP захтева механизам композиционог корена — тачку у апликацији где се креирају све зависности и повезују једна са другом. У Android-у је то Application.get() или Hilt компонента, у iOS-у — AppDelegate или SceneDelegate. Композициони корен је једино место где код зна за конкретне имплементације.

Изолација слојева кроз DIP

DIP формира архитектонске границе између слојева апликације. Када ViewModel зависи од интерфејса UserRepository, између presentation и domain слоја настаје граница: ViewModel (presentation) не зна одакле долазе подаци. Ова граница омогућава промену имплементације UserRepository (Room → REST → Mock) без утицаја на ViewModel. Што је више таквих граница, апликација је отпорнија на промене фрејмворка и библиотека.

У Android архитектури коју препоручује Google, DIP се реализује кроз UseCase, који се налазе у domain слоју и зависе од интерфејса Repository. RepositoryImpl се налазе у data слоју и имплементирају ове интерфејсе. Presentation слој (ViewModel) зависи од UseCase. Смер зависности иде од presentation ка domain, од domain ка data — али ниједан слој не зна за конкретне имплементације другог слоја.

Разлика између DIP и DI (Dependency Injection)

DIP и DI се често мешају, али су различити концепти. DIP — архитектонски принцип (ШТА треба радити: зависити од апстракција). DI — образац имплементације (КАКО то урадити: преносити зависности кроз конструктор). DIP одговара на питање на чему треба да се заснивају модули?, DI — како објекти добијају своје зависности?.

Dependency Injection — начин убризгавања зависности у објекат кроз конструктор, метод или својство. Када се у Kotlin класу интерфејс Repository преноси кроз конструктор — то је DI. А то што класа ViewModel зависи од интерфејса Repository, а не од конкретне имплементације RoomRepository — то је DIP. DI — алат, DIP — циљ.

Може се поштовати DIP без DI фрејмворка: ручно повезивање зависности у композиционом корену је такође DI (manual DI). Може се користити DI фрејмворк (Dagger, Hilt, Koin) док се DIP крши: ако ViewModel директно креира објекат Repository кроз new() — DIP је прекршен, чак и ако је фрејмворк инсталиран. DIP — архитектонска одлука, DI — технички детаљ.

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

Размотримо Android пример примене DIP на слој података. Без DIP, ViewModel директно креира RoomDatabase и DAO. Са DIP — ViewModel зависи од интерфејса UserRepository, а конкретна имплементација RoomUserRepository се доставља споља.

kotlin
// Апстракција припада domain слоју (виши ниво)
interface UserRepository {
    fun getUser(id: Int): User
}

// Domain слој зависи само од апстракције
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Имплементација у data слоју зависи од апстракције domain слоја
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)
    }
}

iOS пример са Application Coordinator и протоколом за навигацију:

swift
// Апстракција навигације у domain слоју
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 слој) имплементира протокол domain слоја
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit код навигације
    }
    func navigateToLogin() {
        // UIKit код навигације
    }
}

Кључни моменат: AuthViewModel (domain) не зна за постојање AppCoordinator (UIKit). Он зна само протокол AuthNavigation. Ако сутра UIKit буде замењен SwiftUI-јем — AuthViewModel не захтева измене. DIP чини domain слој независним од фрејмворка и UI библиотека.

Алати за DIP: Dagger, Hilt, Koin

Hilt — стандардни DI алат за Android, који препоручује Google. Уграђен у Jetpack, подржава ViewModel, Fragment, Service и друге Android компоненте. Hilt аутоматизује креирање композиционог корена кроз анотације @Module, @Provides, @Inject. Коришћење Hilt-а не гарантује поштовање DIP — интерфејс UserRepository мора бити дефинисан у domain слоју, а не у data слоју.

Koin — лагани DI фрејмворк за Kotlin без генерисања кода и процесора анотација. Koin DSL (module, single, factory) је једноставнији за учење, али провера зависности се одвија у рантајму, а не у фази компилације. Koin је популаран у мултиплатформским пројектима (KMP) због подршке за iOS.

Dagger 2 — претходник Hilt-а, још увек се користи у великим пројектима. Dagger генерише DI код у фази компилације, што даје максималне перформансе и дијагностику грешака у фази изградње. Hilt је изграђен на Dagger-у и пружа поједностављени API. За нове пројекте Google препоручује Hilt као главни DI фрејмворк.

Организација DI модула по слојевима

DI модули треба да одговарају архитектонским слојевима и буду подељени на DomainModule, DataModule, PresentationModule. DomainModule пружа само апстракције и UseCase. DataModule пружа имплементације за апстракције. PresentationModule повезује ViewModel са UseCase. Оваква организација гарантује да domain слој остаје независан од инфраструктурних библиотека.

При миграцији између DI фрејмворка (на пример, са Koin на Hilt) структура DomainModule се не мења — мењају се само начини повезивања у DataModule и PresentationModule. DIP обезбеђује изолацију domain логике, а DI фрејмворк је технички механизам повезивања.

Често постављана питања

Да ли увек треба примењивати DIP?

DIP је неопходан на архитектонским границама — између слојева апликације (domain → data, presentation → domain). Унутар једног слоја DIP може бити сувишан. На пример, утилитарна класа StringFormatter унутар domain слоја не захтева интерфејс — ако нема предуслова за њену замену.

Да ли је DIP исто што и убризгавање зависности?

Не. DIP — принцип: модули треба да зависе од апстракција. DI — образац: објекат добија зависности споља, а не ствара их сам. DI — начин имплементације DIP, али DIP се може поштовати и без DI (кроз фабрике или сервис локатор). DI без DIP је могућ, али нема архитектонску вредност.

Где дефинисати интерфејсе за DIP?

Интерфејси припадају модулу који их користи, а не оном који их имплементира. UserRepository се декларише у domain слоју, а имплементира у data слоју. Ово је кључно правило DIP: власник апстракције је потрошач, а не добављач имплементације.

Како DIP утиче на тестирање?

DIP омогућава тестирање на изолованим слојевима. ViewModel који зависи од UserRepository (интерфејс) тестира се са mock имплементацијом без базе података. Без DIP, ViewModel би зависио од RoomUserRepository и захтевао подешавање базе података за сваки тест. DIP + DI пружају потпуну изолацију модула при тестирању.

Који DI фрејмворк одабрати за Android?

Hilt — стандардни избор за Android пројекте, препоручен од Google-а. Koin — алтернатива за Kotlin Multiplatform пројекте. Dagger 2 — за постојеће пројекте где миграција на Hilt није оправдана. Избор фрејмворка не поништава потребу поштовања DIP на нивоу архитектуре.

Резиме

  • DIP (Dependency Inversion Principle) — пети принцип SOLID о архитектонским границама кроз апстракције
  • Модули вишег нивоа не зависе од модула нижег нивоа — оба зависе од апстракција
  • DIP ≠ DI: принцип против обрасца имплементације; DI — начин, DIP — циљ
  • Апстракције припадају потрошачу (domain слој), а не добављачу (data слој)
  • Композициони корен — једино место у апликацији где се прикупљају конкретне зависности
  • Hilt, Koin, Dagger — DI алати који аутоматизују повезивање, али не замењују архитектонску одлуку DIP
  • Domain слој изграђен по DIP-у остаје независан од фрејмворка, UI и инфраструктурних библиотека

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође