Bağımlılık Enjeksiyonu (DI), bir nesnenin bağımlılıklarını kendisinin oluşturması yerine dışarıdan alması tekniğidir. DI, IoC (Kontrolün Tersine Çevrilmesi) ilkesinin bir uygulamasıdır ve Dagger, Hilt ile Swinject'in temelini oluşturur. Bağımlılık enjeksiyonu kod bağımlılığını azaltır, test etmeyi basitleştirir ve mimariyi esnek hale getirir. Android'de DI, Google'ın Dagger Hilt'i ile standarttır; iOS'te ise Swinject veya manuel enjeksiyon ile yapılır. Daha fazla bilgi için Android DI Kılavuzu'na bakın.
Önemli Noktalar
Bağımlılık Enjeksiyonu, bir nesnenin bağımlılıkları (servisler, depolar, yapılandırmalar) bir kurucu, ayarlayıcı veya arayüz aracılığıyla alması, bunları new ile kendisinin oluşturması yerine geçen bir tekniktir. DI'nin amacı sınıflar arasındaki bağımlılığı azaltmaktır. Bir sınıf bağımlılıkları kendisi oluşturuyorsa, belirli uygulamalara sıkı sıkıya bağlı hale gelir ve bu da test etmeyi ve değişiklik yapmayı zorlaştırır. DI ile sınıf bir soyutlama (protokol/arayüz) ile çalışır ve somut uygulama dışarıdan sağlanır.
Üç enjeksiyon yöntemi — Kurucu Enjeksiyonu (init/constructor aracılığıyla), Ayarlayıcı Enjeksiyonu (özellik/setter aracılığıyla), Arayüz Enjeksiyonu (bir arayüz metodu aracılığıyla). Kurucu Enjeksiyonu tercih edilen yöntemdir: bağımlılıklar imzada açıkça görünür ve nesne her zaman geçerli bir durumda oluşturulur. Ayarlayıcı Enjeksiyonu, varsayılan değere sahip isteğe bağlı bağımlılıklar için kullanılır. Arayüz Enjeksiyonu nadirdir, çoğunlukla DI konteynerleri için kullanılır.
| DI Türü | Yöntem | Ne Zaman Kullanılır | Örnek |
|---|---|---|---|
| Kurucu | Başlatıcı parametreleri | Zorunlu bağımlılıklar | init(service: ServiceProtocol) |
| Özellik | Sınıf özelliği | İsteğe bağlı bağımlılıklar | var service: ServiceProtocol? |
| Metot | Metot parametresi | Geçici bağımlılıklar | func doWork(with service: Service) |
DI Konteyneri — bağımlılıkların oluşturulmasını ve yaşam döngüsünü yöneten bir kütüphane. Konteyner, tür kayıtlarını (her soyut tür somut bir uygulamayla eşlenir) ve çözülmüş bağımlılıklarla nesneler oluşturmak için bir fabrika içerir. Android'de — Dagger/Hilt, iOS'te — Swinject, Needle, Dip. Konteyner kapsamı (scope) yönetebilir: singleton (uygulama başına bir örnek), özellik kapsamı (ekran başına) veya her istekte yeni bir nesne.
Dagger Hilt, Google'ın Dagger'ı üzerine bir sarmalayıcı olan Android için standart DI kütüphanesidir. Hilt, Dagger'ı basitleştirir: manuel bileşen oluşturmayı kaldırır, @HiltAndroidApp, @AndroidEntryPoint ve @Module ekler. Hilt, Android yaşam döngüsüyle entegre olur: ViewModel, Activity, Fragment, Service, BroadcastReceiver ek açıklamalar aracılığıyla bağımlılıkları alabilir. Kod üretimi derleme zamanında gerçekleşir — Dagger bileşen uygulamalarını oluşturur ve bu da sıfır runtime yükü sağlar.
// Uygulama sınıfı
@HiltAndroidApp
class MyApp : Application()
// Modül — bağımlılıkların nasıl oluşturulacağını tanımlar
@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, kurucu aracılığıyla bağımlılık 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'yı etkinleştirir
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Dagger bileşenleri ve kapsamları — @Singleton (tüm uygulama), @ActivityScoped (Activity başına), @FragmentScoped (Fragment başına), @ViewModelScoped (ViewModel başına). Kapsam seçimi nesnenin ömrünü belirler. @Singleton — işlem başına bir örnek, OkHttpClient ve veritabanları için uygundur. @ActivityScoped — nesne, Activity yaşadığı sürece yaşar, ekran düzeyindeki bağımlılıklar için. @ViewModelScoped — Hilt 2.45+'da yeni, nesne ViewModel yaşadığı sürece yaşar, coroutine kapsamları için kullanışlıdır.
Swinject, iOS için popüler bir açık kaynak DI framework'üdür. Swinject, Container, Assemblies ve çeşitli kapsamlar sağlar. Dagger'ın aksine, Swinject çalışma zamanında çalışır — bağımlılıklar kod üretimi olmadan dinamik olarak çözülür. Bu, Swinject'i kurmayı kolaylaştırır, ancak hata ayıklamayı zorlaştırır: çözülmemiş bağımlılık hatası yalnızca çalışma zamanında görünür. Swinject, Kurucu Enjeksiyonu, Özellik Enjeksiyonu ve Metot Enjeksiyonunu destekler.
import Swinject
// Assembly — bir kayıt grubu
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, Kurucu Enjeksiyonu aracılığıyla
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 veya Uygulamada DI kurulumu
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// UIKit ViewController için Özellik Enjeksiyonu
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Swinject kapsamları — .transient (her seferinde yeni nesne), .container (konteyner başına singleton), .graph (varsayılan — nesne tek bir bağımlılık grafiği içinde paylaşılır). iOS uygulamaları için .container ve .transient yeterlidir. Swinject ayrıca Assembler'ı da destekler — modüler mimari için Assembly gruplaması. Testler için Assembly, MockAssembly ile değiştirilir ve üretim kodunu değiştirmeden bağımlılık değişimine olanak tanır.
DI vs Service Locator — her iki desen de bağımlılık yönetimi sorununu çözer, ancak farklı şekillerde. DI, bağımlılıkları nesneye enjekte eder; Service Locator, nesnenin bağımlılıkları kendisinin talep ettiği küresel bir kayıt defteri sağlar. DI, bağımlılıkları kurucu (veya ayarlayıcı) aracılığıyla açıkça bildirir. Service Locator, bağımlılıkları gizler — bunlar metot içinde talep edilir ve imzayı daha az bilgilendirici hale getirir. DI test etmesi daha kolaydır: kurucuya bir mock geçirmek yeterlidir. Service Locator, her test için küresel kayıt defterinin ayarlanmasını gerektirir.
| Özellik | Bağımlılık Enjeksiyonu | Service Locator | Manuel Enjeksiyon |
|---|---|---|---|
| Bağımlılıkların görünürlüğü | Kurucuda | Metot gövdesinde gizli | Açık |
| Test | Kurucuda Mock | Locator ayarı | Kurucuda Mock |
| Kurulum karmaşıklığı | DI konteyneri gerekli | Küresel kayıt defteri | Manuel oluşturma |
| Runtime yükü | Dagger — derleme zamanı | Runtime arama | Yok |
DI vs manuel enjeksiyon — DI konteyneri olmadan, bağımlılıklar fabrikalarda veya AppDelegate'te manuel olarak oluşturulur. 5-10 sınıf için manuel enjeksiyon daha basittir — Dagger veya Swinject öğrenmeyi gerektirmez. 50+ sınıf için manuel enjeksiyon sorunlu hale gelir: 5-6 parametreli kurucular, karmaşık oluşturma sırası, kod tekrarı. Bir DI konteyneri bu süreçleri otomatikleştirir ve net bir yaşam döngüsü yönetimi sağlar. Konteyner olmadan manuel enjeksiyon, küçük projeler ve prototipler için iyi bir seçimdir.
Kurucu Enjeksiyonu — standart. Zorunlu bağımlılıklar için her zaman Kurucu Enjeksiyonunu kullanın. Bu, bağımlılıkları açık hale getirir ve nesne her zaman çalışmaya hazır olur. Ayarlayıcı Enjeksiyonu — yalnızca isteğe bağlı bağımlılıklar için (örneğin, delegate veya listener). Arayüz Enjeksiyonu — kendi DI kütüphanenizi yazmıyorsanız kullanmayın. Kurucu Enjeksiyonu, bir nesnenin geçerli bir durumda oluşturulmasını garanti etmenin tek yoludur.
Bir sınıf — bir sorumluluk. Bir sınıfın kurucusu 5+ parametre gerektiriyorsa, sınıf muhtemelen Tek Sorumluluk İlkesini ihlal ediyordur. Sınıfı daha az bağımlılığı olan birden çok sınıfa bölün. Bir işaret: 6 farklı hizmete sahip bir ServiceManager sınıfı yazıyorsanız — bu God Object anti-desenidir. İş mantığını Use Cases'lere (Interactors) çıkarın, her biri 1-2 bağımlılığa sahip olsun.
// ❌ Kötü: 6 bağımlılık — 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
)
// ✅ İyi: 1-2 bağımlılıklı Use Cases
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Kapsam ve yaşam döngüsü — her bağımlılık için doğru kapsamı seçin. Singleton'lar: OkHttpClient, veritabanı, SharedPreferences. Özellik kapsamı: depolar, Use Cases (durumsuz iseler). Transient: Value Objects, DateFormatter, ayrıştırıcılar. Kapsam hataları yaygın bir sorundur: ekran durumunu depolayan bir singleton, sızıntılara neden olur. Android'de, Hilt'in @ActivityScoped'i bunu çözer; Swinject'te .container'ı dikkatli kullanın.
Sıkça Sorulan Sorular
new, sınıflar arasında sıkı bir bağ oluşturur — kodu değiştirmeden uygulamayı değiştiremezsiniz. Test etmek zorlaşır: gerçek bir hizmet yerine mock enjekte edemezsiniz. SRP ihlal edilir: sınıf hem iş mantığından hem de bağımlılık oluşturmadan sorumludur. DI, bağımlılıkları dışarıdan enjekte ederek ve soyutlamalarla çalışarak bu sorunları çözer.
Dagger Hilt, Google'ın standardıdır, kod üretimi ile derleme zamanı DI, daha iyi performans ve Jetpack entegrasyonu sağlar. Koin, çalışma zamanı DI'dır, kurulumu daha kolaydır ancak daha yavaştır ve çalışma zamanı hatalarına sahiptir. Üretim projeleri için Hilt'i seçin. Koin, prototipler ve küçük uygulamalar için uygundur.
Hayır. iOS için mevcut olanlar: Swinject (çalışma zamanı, popüler), Needle (Uber'dan derleme zamanı), Dip (hafif), Weaver (Sourcery tabanlı). Apple, yerleşik bir DI konteyneri sağlamaz, ancak init aracılığıyla manuel enjeksiyon standart bir uygulamadır. SwiftUI için, harici kütüphaneler olmadan Environment veya @StateObject aracılığıyla manuel DI genellikle yeterlidir.
Evet. Kurucu aracılığıyla manuel enjeksiyon, framework olmadan DI'dır. Service Locator, framework olmadan bir alternatiftir. Fabrikalar ve Factory Method da DI biçimleridir. Bir framework (Dagger, Swinject), rutin kayıt ve bağımlılık çözümünü otomatikleştirir, ancak 10-20 sınıf için manuel DI yeterlidir.
DI, Kontrolün Tersine Çevrilmesi ilkesini uygulayan bir tekniktir (şablondur). GoF desenlerinin aksine, DI'nın katı bir 3-4 sınıf yapısı yoktur. DI, bağımlılıkları düzenlemenin bir yoludur, bir tasarım deseni değildir. DI konteynerleri (Dagger, Swinject), bu tekniği otomatikleştiren framework'lerdir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun