Dependency Injection (DI, убризгавање зависности) — техника при којој објекат прима своје зависности споља, уместо да их сам креира. DI је имплементација принципа IoC (Inversion of Control) и лежи у основи Dagger, Hilt и Swinject. Убризгавање зависности смањује повезаност кода, поједностављује тестирање и чини архитектуру флексибилном. У Android DI је стандард кроз Dagger Hilt од Google-а, у iOS — кроз Swinject или ручно убризгавање. Више — у Android DI Guide.
Главно
Dependency Injection — техника при којој објекат прима зависности (сервисе, репозиторијуме, конфигурације) кроз конструктор, сетер или интерфејс, уместо да их сам креира кроз new. Циљ DI је смањење повезаности (coupling) између класа. Ако класа сама креира зависности, она је чврсто везана за конкретне имплементације, што отежава тестирање и модификацију. Код DI класа ради са апстракцијом (protocol/interface), а конкретна имплементација се доставља споља.
Три начина убризгавања — Constructor Injection (кроз init/constructor), Setter Injection (кроз својство/сетер), Interface Injection (кроз метод интерфејса). Constructor Injection — пожељни начин: зависности су јасно видљиве у потпису, објекат се увек креира у валидном стању. Setter Injection се користи за опционе зависности са подразумеваном вредношћу. Interface Injection — ретко, углавном за DI контејнере.
| Врста DI | Начин | Када користити | Пример |
|---|---|---|---|
| Constructor | Параметри иницијализатора | Обавезне зависности | init(service: ServiceProtocol) |
| Property | Својство класе | Опционе зависности | var service: ServiceProtocol? |
| Method | Параметар методе | Привремене зависности | func doWork(with service: Service) |
DI контејнер — библиотека која управља креирањем и животним циклусом зависности. Контејнер садржи регистрацију типова (сваки апстрактни тип је мапиран на конкретну имплементацију) и фабрику за креирање објеката са решеним зависностима. У Android — Dagger/Hilt, у iOS — Swinject, Needle, Dip. Контејнер може управљати опсегом (Scope): синглтон (једна инстанца по апликацији), опсег функције (по екрану) или нови објекат при сваком захтеву.
Dagger Hilt — слој изнад Dagger од Google-а, стандардна DI библиотека за Android. Hilt поједностављује Dagger: уклања ручно креирање компонената, додаје @HiltAndroidApp, @AndroidEntryPoint и @Module. Hilt се интегрише са животним циклусом Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver могу примати зависности кроз анотације. Генерисање кода се одвија у фази компилације — Dagger генерише имплементације компонената, што даје нулти runtime overhead.
// Application class
@HiltAndroidApp
class MyApp : Application()
// Module — дефинише како се креирају зависности
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
@Provides
@Singleton
fun provideApiService(client: OkHttpClient): ApiService {
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(client)
.build()
.create(ApiService::class.java)
}
}
// ViewModel прима зависност кроз конструктор
@HiltViewModel
class MainViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _state = MutableStateFlow(MainState.Loading)
val state: StateFlow<MainState> = _state.asStateFlow()
fun loadData() {
viewModelScope.launch {
_state.value = MainState.Success(apiService.getData())
}
}
}
// Activity — @AndroidEntryPoint омогућава DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Dagger компоненте и опсези видљивости — @Singleton (за целу апликацију), @ActivityScoped (за Activity), @FragmentScoped (за Fragment), @ViewModelScoped (за ViewModel). Избор опсега одређује време живота објекта. @Singleton — једна инстанца по процесу, погодна за OkHttpClient и базу података. @ActivityScoped — објекат живи док живи Activity, за екранске зависности. @ViewModelScoped — новина Hilt 2.45+, објекат живи док живи ViewModel, што је згодно за корутинске опсеге.
Swinject — популарни DI оквир за iOS са отвореним кодом. Swinject пружа Container, Assemblies и различите опсеге. За разлику од Dagger, Swinject ради у runtime-у — зависности се решавају динамички без генерисања кода. То чини Swinject једноставнијим за подешавање, али отежава отклањање грешака: грешка нерешене зависности се појављује тек у runtime-у. Swinject подржава Constructor Injection, Property Injection и Method Injection.
import Swinject
// Assembly — група регистрација
class NetworkAssembly: Assembly {
func assemble(container: Container) {
container.register(NetworkServiceProtocol.self) { _ in
NetworkService()
}.inObjectScope(.container) // singleton
container.register(UserRepositoryProtocol.self) { r in
UserRepository(
networkService: r.resolve(NetworkServiceProtocol.self)!
)
}
}
}
// ViewModel кроз Constructor Injection
class ProfileViewModel: ObservableObject {
private let repository: UserRepositoryProtocol
init(repository: UserRepositoryProtocol) {
self.repository = repository
}
@Published var user: User?
func loadUser() {
repository.fetchUser { [weak self] user in
self?.user = user
}
}
}
// Подешавање DI у AppDelegate или App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// Property Injection за UIKit ViewController
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Swinject опсези — .transient (нови објекат сваки пут), .container (синглтон на контејнер), .graph (подразумевано — објекат се дели унутар једног графа зависности). За iOS апликације довољни су .container и .transient. Swinject такође подржава Assembler — груписање Assembly за модуларну архитектуру. За тестове Assembly се замењују са MockAssembly, што омогућава замену зависности без промене производног кода.
DI vs Service Locator — оба обрасца решавају проблем управљања зависностима, али на различите начине. DI убризгава зависности у објекат, Service Locator пружа глобални регистар из ког објекат сам преузима зависности. DI јасно декларише зависности кроз конструктор (или сетер). Service Locator скрива зависности — one се преузимају унутар методе, што чини потпис мање информативним. DI се лакше тестира: довољно је ставити mock у конструктор. Service Locator захтева подешавање глобалног регистра за сваки тест.
| Карактеристика | Dependency Injection | Service Locator | Ручно убризгавање |
|---|---|---|---|
| Очигледност зависности | У конструктору | Скривене у телу методе | Очигледне |
| Тестирање | Mock у конструктору | Подешавање Locator-а | Mock у конструктору |
| Сложеност подешавања | Захтева DI контејнер | Глобални регистар | Ручно креирање |
| Runtime overhead | Dagger — compile-time | Runtime lookup | Нема |
DI vs ручно убризгавање — без DI контејнера, зависности се креирају ручно у фабрикама или AppDelegate. За 5-10 класа ручно убризгавање је једноставније — не захтева учење Dagger или Swinject. За 50+ класа ручно убризгавање постаје проблем: конструктори са 5-6 параметара, сложен редослед креирања, дуплирање кода. DI контејнер аутоматизује ове процесе и пружа јасан lifecycle. Ручно убризгавање без контејнера — добар избор за мале пројекте и прототипове.
Constructor Injection — стандард. Увек користите Constructor Injection за обавезне зависности. То чини зависности јасним и објекат увек спремним за рад. Setter Injection — само за опционе зависности (нпр. delegate или listener). Interface Injection — не користите, осим ако не пишете сопствену DI библиотеку. Constructor Injection — једини начин да се гарантује да се објекат креира у валидном стању.
Једна класа — једна одговорност. Ако конструктор класе захтева 5+ параметара, вероватно класа крши Single Responsibility Principle. Поделите класу на неколико са мање зависности. Знак: ако пишете класу ServiceManager са 6 различитих сервиса — то је анти-образац God Object. Издвојите пословну логику у Use Cases (Interactors), сваки са 1-2 зависности.
// ❌ Лоше: 6 зависности — God Object
class ProfileViewModel @Inject constructor(
private val api: ApiService,
private val db: Database,
private val analytics: Analytics,
private val prefs: Preferences,
private val location: LocationProvider,
private val notification: NotificationManager
)
// ✅ Добро: Use Cases са 1-2 зависности
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Опсег и lifecycle — бирајте одговарајући опсег за сваку зависност. Синглтони: OkHttpClient, база података, SharedPreferences. Опсег функције: репозиторијуми, Use Cases (ако немају стање). Transient: Value Objects, DateFormatter, парсери. Грешка опсега — чест проблем: синглтон који чува стање екрана доводи до цурења меморије. У Android Hilt @ActivityScoped решава овај проблем, у Swinject — .container са опрезом.
Често постављана питања
new ствара чврсту везу између класа — не можете заменити имплементацију без промене кода. Тешко је тестирати: не можете убацити mock уместо правог сервиса. SRP се крши: класа одговара и за пословну логику и за креирање зависности. DI решава ове проблеме кроз убризгавање зависности споља и рад са апстракцијама.
Dagger Hilt — стандард од Google-а, compile-time DI са генерисањем кода, боље перформансе и интеграција са Jetpack. Koin — runtime DI, једноставнији за подешавање, али спорији и са грешкама у runtime-у. Бирејте Hilt за производне пројекте. Koin је погодан за прототипове и мале апликације.
Не. За iOS су доступни: Swinject (runtime, популаран), Needle (compile-time од Uber-а), Dip (лагани), Weaver (заснован на Sourcery). Apple не пружа уграђени DI контејнер, али ручно убризгавање кроз init је стандардна пракса. За SwiftUI често је довољно ручно DI кроз Environment или @StateObject без спољних библиотека.
Да. Ручно убризгавање кроз конструктор — то је DI без оквира. Service Locator — алтернатива без оквира. Фабрике и Factory Method — такође облик DI. Оквир (Dagger, Swinject) аутоматизује рутинску регистрацију и решавање зависности, али за 10-20 класа ручни DI је довољан.
DI — техника (образац) која имплементира принцип Inversion of Control. За разлику од GoF образаца, DI нема строгу структуру од 3-4 класе. DI је начин организовања зависности, а не дизајн образац. DI контејнери (Dagger, Swinject) су оквири који аутоматизују ову технику.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође