Dependency Injection (DI, asılılıqların inject edilməsi) — obyektin asılılıqlarını özü yaratmayıb xaricdən aldığı texnikadır. DI IoC (Inversion of Control) prinsipinin tətbiqidir və Dagger, Hilt və Swinject-in əsasında durur. Asılılıqların inject edilməsi kodun bağlılığını azaldır, testləri sadələşdirir və arxitekturanı çevik edir. Android-də DI standart olaraq Google-un Dagger Hilt-i ilə, iOS-da isə Swinject və ya əl ilə inject etmə ilə həyata keçirilir. Ətraflı — Android DI Guide.
Əsas məqamlar
Dependency Injection — obyektin asılılıqları (servislər, repozitoriyalar, konfiqurasiyalar) konstruktor, setter və ya interfeys vasitəsilə aldığı, özü new ilə yaratmadığı texnikadır. DI-nin məqsədi siniflər arasında bağlılığı (coupling) azaltmaqdır. Əgər sinif asılılıqları özü yaradırsa, o, konkret tətbiqlərə sərt bağlanır ki, bu da test etməni və dəyişikliyi çətinləşdirir. DI-də sinif abstraksiya (protocol/interface) ilə işləyir, konkret tətbiq isə xaricdən verilir.
Üç inject etmə üsulu — Constructor Injection (init/constructor vasitəsilə), Setter Injection (xüsusiyyət/setter vasitəsilə), Interface Injection (interfeys metodu vasitəsilə). Constructor Injection — üstünlük verilən üsul: asılılıqlar imzada aydın görünür, obyekt həmişə etibarlı vəziyyətdə yaradılır. Setter Injection isteğe bağlı asılılıqlar üçün default dəyərlə istifadə olunur. Interface Injection — nadir, əsasən DI konteynerləri üçün.
| DI növü | Üsul | Nə vaxt istifadə olunur | Nümunə |
|---|---|---|---|
| Constructor | İnisializator parametrləri | Məcburi asılılıqlar | init(service: ServiceProtocol) |
| Property | Sinif xüsusiyyəti | İsteğe bağlı asılılıqlar | var service: ServiceProtocol? |
| Method | Metod parametri | Müvəqqəti asılılıqlar | func doWork(with service: Service) |
DI konteyneri — asılılıqların yaradılmasını və həyat dövrünü idarə edən kitabxana. Konteyner tiplərin qeydiyyatını (hər abstrakt tip konkret tətbiqlə əlaqələndirilir) və həll edilmiş asılılıqlarla obyektlərin yaradılması üçün fabriki ehtiva edir. Android-də — Dagger/Hilt, iOS-da — Swinject, Needle, Dip. Konteyner skopu (Scope) idarə edə bilər: singleton (tətbiq üçün bir instansiya), funksiya skopu (ekran üçün) və ya hər sorğuda yeni obyekt.
Dagger Hilt — Google-un Dagger üzərində qurulmuş qatı, Android üçün standart DI kitabxanası. Hilt Dagger-i sadələşdirir: komponentlərin əl ilə yaradılmasını aradan qaldırır, @HiltAndroidApp, @AndroidEntryPoint və @Module əlavə edir. Hilt Android həyat dövrü ilə inteqrasiya olunur: ViewModel, Activity, Fragment, Service, BroadcastReceiver annotasiyalar vasitəsilə asılılıqları ala bilər. Kodgenerasiya kompilyasiya mərhələsində baş verir — Dagger komponentlərin implementasiyalarını yaradır ki, bu da sıfır runtime overhead verir.
// Application class
@HiltAndroidApp
class MyApp : Application()
// Module — asılılıqların necə yaradılacağını müəyyən edir
@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 asılılığı konstruktor vasitəsilə alır
@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-ni aktivləşdirir
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Dagger komponentləri və görünmə sahələri — @Singleton (bütün tətbiq üçün), @ActivityScoped (Activity üçün), @FragmentScoped (Fragment üçün), @ViewModelScoped (ViewModel üçün). Skop seçimi obyektin ömrünü müəyyən edir. @Singleton — proses üçün bir instansiya, OkHttpClient və verilənlər bazası üçün uyğundur. @ActivityScoped — obyekt Activity yaşadıqca yaşayır, ekran asılılıqları üçün. @ViewModelScoped — Hilt 2.45+ yeniliyi, obyekt ViewModel yaşadıqca yaşayır, korutin skopları üçün əlverişlidir.
Swinject — iOS üçün açıq mənbəli məşhur DI freymvorku. Swinject Container, Assemblies və müxtəlif skoplar təqdim edir. Dagger-dən fərqli olaraq, Swinject runtime-da işləyir — asılılıqlar kodgenerasiya olmadan dinamik həll edilir. Bu, Swinject-i konfiqurasiyada sadələşdirir, lakin debug-da çətinlik yaradır: həll edilməmiş asılılıq xətası yalnız runtime-da üzə çıxır. Swinject Constructor Injection, Property Injection və Method Injection dəstəkləyir.
import Swinject
// Assembly — qeydiyyat qrupu
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 vasitəsilə
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
}
}
}
// AppDelegate və ya App-də DI konfiqurasiyası
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// UIKit ViewController üçün Property Injection
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Swinject skopları — .transient (hər dəfə yeni obyekt), .container (konteyner üçün singleton), .graph (defolt — obyekt bir asılılıq qrafiki daxilində paylaşılır). iOS tətbiqləri üçün .container və .transient kifayətdir. Swinject həmçinin Assembler-i dəstəkləyir — modul arxitekturası üçün Assembly-lərin qruplaşdırılması. Testlər üçün Assembly-lər MockAssembly ilə əvəz olunur ki, bu da istehsalat kodunu dəyişmədən asılılıqları əvəz etməyə imkan verir.
DI vs Service Locator — hər iki naxış asılılıqların idarə edilməsi problemini həll edir, lakin fərqli şəkildə. DI asılılıqları obyektə inject edir, Service Locator isə qlobal reyestr təqdim edir, obyekt asılılıqları özü oradan götürür. DI asılılıqları konstruktor (və ya setter) vasitəsilə açıq şəkildə bəyan edir. Service Locator asılılıqları gizlədir — onlar metodun daxilində götürülür ki, bu da imzanı daha az informativ edir. DI-ni test etmək asandır: konstruktora mock vermək kifayətdir. Service Locator hər test üçün qlobal reyestrin konfiqurasiyasını tələb edir.
| Xüsusiyyət | Dependency Injection | Service Locator | Əl ilə inject etmə |
|---|---|---|---|
| Asılılıqların aşkarlığı | Konstruktorda | Metod gövdəsində gizli | Aşkar |
| Test etmə | Konstruktorda Mock | Locator-un konfiqurasiyası | Konstruktorda Mock |
| Konfiqurasiya mürəkkəbliyi | DI konteyneri tələb edir | Qlobal reyestr | Əl ilə yaratma |
| Runtime overhead | Dagger — compile-time | Runtime lookup | Yoxdur |
DI vs əl ilə inject etmə — DI konteyneri olmadan asılılıqlar fabriklərdə və ya AppDelegate-də əl ilə yaradılır. 5-10 sinif üçün əl ilə inject etmə daha sadədir — Dagger və ya Swinject öyrənmək tələb olunmur. 50+ sinif üçün əl ilə inject etmə problemə çevrilir: 5-6 parametrli konstruktorlar, mürəkkəb yaratma ardıcıllığı, kodun təkrarlanması. DI konteyneri bu prosesləri avtomatlaşdırır və aydın lifecycle təmin edir. Konteynersiz əl ilə inject etmə — kiçik layihələr və prototiplər üçün yaxşı seçimdir.
Constructor Injection — standart. Məcburi asılılıqlar üçün həmişə Constructor Injection istifadə edin. Bu, asılılıqları aşkar edir və obyekti həmişə işə hazır vəziyyətdə saxlayır. Setter Injection — yalnız isteğe bağlı asılılıqlar üçün (məsələn, delegate və ya listener). Interface Injection — öz DI kitabxananızı yazmırsınızsa, istifadə etməyin. Constructor Injection — obyektin etibarlı vəziyyətdə yaradılmasını təmin edən yeganə üsuldur.
Bir sinif — bir məsuliyyət. Sinif konstruktoru 5+ parametr tələb edirsə, çox güman ki, sinif Single Responsibility Principle-i pozur. Sinifi daha az asılılığı olan bir neçə hissəyə bölün. Əlamət: 6 fərqli servisli ServiceManager sinfi yazırsınızsa — bu God Object anti-naxışıdır. Biznes məntiqini Use Cases (Interactors) ilə ayırın, hər biri 1-2 asılılıqla.
// ❌ Pis: 6 asılılıq — 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
)
// ✅ Yaxşı: 1-2 asılılığı olan Use Cases
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Skop və lifecycle — hər asılılıq üçün düzgün skop seçin. Singleton: OkHttpClient, verilənlər bazası, SharedPreferences. Funksiya skopu: repozitoriyalar, Use Cases (əgər vəziyyəti yoxdursa). Transient: Value Objects, DateFormatter, parserlər. Skop səhvi — tez-tez rast gəlinən problem: ekran vəziyyətini saxlayan singleton sızmalara səbəb olur. Android Hilt-də @ActivityScoped bu problemi həll edir, Swinject-də — .container ehtiyatla.
Tez-tez verilən suallar
new siniflər arasında sərt bağlılıq yaradır — kodu dəyişmədən tətbiqi əvəz edə bilməzsiniz. Test etmək çətindir: real servis əvəzinə mock qoya bilməzsiniz. SRP pozulur: sinif həm biznes məntiqinə, həm də asılılıqların yaradılmasına cavabdeh olur. DI bu problemləri asılılıqları xaricdən inject etməklə və abstraksiyalarla işləməklə həll edir.
Dagger Hilt — Google-dan standart, kodgenerasiya ilə compile-time DI, daha yaxşı performans və Jetpack ilə inteqrasiya. Koin — runtime DI, konfiqurasiyada daha sadə, lakin daha yavaş və runtime səhvləri ilə. İstehsalat layihələri üçün Hilt seçin. Koin prototiplər və kiçik tətbiqlər üçün uyğundur.
Xeyr. iOS üçün mövcuddur: Swinject (runtime, məşhur), Needle (Uber-dən compile-time), Dip (yüngül), Weaver (Sourcery əsaslı). Apple daxili DI konteyneri təqdim etmir, lakin init vasitəsilə əl ilə inject etmə standart təcrübədir. SwiftUI üçün çox vaxt xarici kitabxanalar olmadan Environment və ya @StateObject vasitəsilə əl ilə DI kifayətdir.
Bəli. Konstruktor vasitəsilə əl ilə inject etmə — freymvorksuz DI-dir. Service Locator — freymvorksuz alternativdir. Fabriklər və Factory Method — həmçinin DI formasıdır. Freymvork (Dagger, Swinject) rutin qeydiyyatı və asılılıqların həllini avtomatlaşdırır, lakin 10-20 sinif üçün əl ilə DI kifayətdir.
DI — Inversion of Control prinsipini tətbiq edən texnikadır (naxış). GoF naxışlarından fərqli olaraq, DI 3-4 sinifdən ibarət ciddi struktura malik deyil. DI asılılıqların təşkili üsuludur, dizayn naxışı deyil. DI konteynerləri (Dagger, Swinject) — bu texnikanı avtomatlaşdıran freymvorklərdir.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun