Dependency Injection (DI, dependency injection) — teknik kung saan ang isang bagay ay tumatanggap ng mga dependency nito mula sa labas, sa halip na likhain ang mga ito mismo. Ang DI ay isang implementasyon ng prinsipyong IoC (Inversion of Control) at nasa puso ng Dagger, Hilt at Swinject. Binabawasan ng dependency injection ang pagkakabit ng code, pinapasimple ang pagsubok, at ginagawang flexible ang arkitektura. Sa Android, ang DI ay pamantayan sa pamamagitan ng Dagger Hilt mula sa Google, sa iOS — sa pamamagitan ng Swinject o manu-manong injection. Higit pa — sa Android DI Guide.
Mga Pangunahing Punto
Dependency Injection — teknik kung saan ang isang bagay ay tumatanggap ng mga dependency (mga serbisyo, repositoryo, configuration) sa pamamagitan ng constructor, setter, o interface, sa halip na likhain ang mga ito mismo gamit ang new. Ang layunin ng DI ay bawasan ang pagkakabit (coupling) sa pagitan ng mga klase. Kung ang isang klase ay lumilikha ng sarili nitong mga dependency, ito ay mahigpit na nakatali sa mga konkretong implementasyon, na nagpapahirap sa pagsubok at pagbabago. Sa DI, ang klase ay gumagana sa isang abstraksyon (protocol/interface), at ang konkretong implementasyon ay ibinibigay mula sa labas.
Tatlong paraan ng injection — Constructor Injection (sa pamamagitan ng init/constructor), Setter Injection (sa pamamagitan ng property/setter), Interface Injection (sa pamamagitan ng interface method). Constructor Injection — ang mas pinipiling paraan: ang mga dependency ay malinaw na nakikita sa lagda, ang bagay ay palaging nilikha sa isang wastong estado. Ang Setter Injection ay ginagamit para sa mga opsyonal na dependency na may default na halaga. Interface Injection — bihira, pangunahin para sa mga DI container.
| Uri ng DI | Paraan | Kailan gagamitin | Halimbawa |
|---|---|---|---|
| Constructor | Mga parameter ng initializer | Mga sapilitang dependency | init(service: ServiceProtocol) |
| Property | Property ng klase | Mga opsyonal na dependency | var service: ServiceProtocol? |
| Method | Parameter ng method | Mga pansamantalang dependency | func doWork(with service: Service) |
DI container — library na namamahala sa paglikha at lifecycle ng mga dependency. Ang container ay naglalaman ng rehistrasyon ng mga uri (bawat abstract na uri ay naka-map sa isang konkretong implementasyon) at pabrika para sa paglikha ng mga bagay na may nalutas na mga dependency. Sa Android — Dagger/Hilt, sa iOS — Swinject, Needle, Dip. Ang container ay maaaring mamahala ng scope: singleton (isang instance bawat aplikasyon), scope ng feature (bawat screen), o bagong bagay sa bawat kahilingan.
Dagger Hilt — layer sa ibabaw ng Dagger mula sa Google, ang pamantayang DI library para sa Android. Pinapasimple ng Hilt ang Dagger: inaalis ang manu-manong paglikha ng mga component, nagdadagdag ng @HiltAndroidApp, @AndroidEntryPoint at @Module. Nakikipag-ugnayan ang Hilt sa lifecycle ng Android: ang ViewModel, Activity, Fragment, Service, BroadcastReceiver ay maaaring tumanggap ng mga dependency sa pamamagitan ng mga annotation. Nagaganap ang code generation sa yugto ng compilation — bumubuo ang Dagger ng mga implementasyon ng mga component, na nagbibigay ng zero runtime overhead.
// Application class
@HiltAndroidApp
class MyApp : Application()
// Module — tumutukoy kung paano gumawa ng mga dependency
@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 ay tumatanggap ng dependency sa pamamagitan ng constructor
@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 ay nag-a-activate ng DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
Mga component ng Dagger at saklaw ng visibility — @Singleton (para sa buong aplikasyon), @ActivityScoped (para sa Activity), @FragmentScoped (para sa Fragment), @ViewModelScoped (para sa ViewModel). Ang pagpili ng scope ay tumutukoy sa habang-buhay ng bagay. @Singleton — isang instance bawat proseso, angkop para sa OkHttpClient at database. @ActivityScoped — ang bagay ay nabubuhay hangga't nabubuhay ang Activity, para sa mga dependency ng screen. @ViewModelScoped — bago sa Hilt 2.45+, ang bagay ay nabubuhay hangga't nabubuhay ang ViewModel, maginhawa para sa mga scope ng coroutine.
Swinject — sikat na DI framework para sa iOS na may open source. Ang Swinject ay nagbibigay ng Container, Assemblies, at iba't ibang scope. Hindi tulad ng Dagger, gumagana ang Swinject sa runtime — ang mga dependency ay nalulutas nang pabago-bago nang walang code generation. Ginagawa nitong mas simple ang Swinject sa configuration, ngunit mahirap i-debug: ang error ng hindi nalutas na dependency ay lilitaw lamang sa runtime. Sinusuportahan ng Swinject ang Constructor Injection, Property Injection, at Method Injection.
import Swinject
// Assembly — grupo ng pagpaparehistro
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 sa pamamagitan ng 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
}
}
}
// Configuration ng DI sa AppDelegate o App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!
// Property Injection para sa UIKit ViewController
container.register(UserViewController.self) { r in
let vc = UserViewController()
vc.viewModel = r.resolve(ProfileViewModel.self)
return vc
}
Mga scope ng Swinject — .transient (bagong bagay sa bawat pagkakataon), .container (singleton bawat container), .graph (default — ang bagay ay ibinabahagi sa loob ng isang dependency graph). Para sa mga iOS application, sapat na ang .container at .transient. Sinusuportahan din ng Swinject ang Assembler — pagpapangkat ng Assembly para sa modular na arkitektura. Para sa mga pagsubok, ang Assembly ay pinapalitan ng MockAssembly, na nagpapahintulot sa pagpapalit ng mga dependency nang hindi binabago ang production code.
DI vs Service Locator — parehong pattern ay lumulutas sa problema ng pamamahala ng mga dependency, ngunit sa magkaibang paraan. Ini-inject ng DI ang mga dependency sa bagay, ang Service Locator ay nagbibigay ng pandaigdigang rehistro kung saan ang bagay ay humihiling ng mga dependency mismo. Ang DI ay tahasang nagdedeklara ng mga dependency sa pamamagitan ng constructor (o setter). Ang Service Locator ay nagtatago ng mga dependency — ang mga ito ay hinihiling sa loob ng method, na ginagawang hindi gaanong impormatibo ang lagda. Ang DI ay mas madaling subukan: sapat na maglagay ng mock sa constructor. Ang Service Locator ay nangangailangan ng configuration ng pandaigdigang rehistro para sa bawat pagsubok.
| Katangian | Dependency Injection | Service Locator | Manu-manong injection |
|---|---|---|---|
| Kalinawan ng mga dependency | Sa constructor | Nakatago sa katawan ng method | Tahasang |
| Pagsubok | Mock sa constructor | Configuration ng Locator | Mock sa constructor |
| Pagiging kumplikado ng configuration | Nangangailangan ng DI container | Pandaigdigang rehistro | Manu-manong paglikha |
| Runtime overhead | Dagger — compile-time | Runtime lookup | Wala |
DI vs manu-manong injection — nang walang DI container, ang mga dependency ay nilikha nang manu-mano sa mga pabrika o AppDelegate. Para sa 5-10 klase, ang manu-manong injection ay mas simple — hindi nangangailangan ng pag-aaral ng Dagger o Swinject. Para sa 50+ klase, ang manu-manong injection ay nagiging problema: mga constructor na may 5-6 parameter, kumplikadong pagkakasunud-sunod ng paglikha, pagdodoble ng code. Ang DI container ay nag-automate sa mga prosesong ito at nagbibigay ng malinaw na lifecycle. Ang manu-manong injection nang walang container — isang magandang pagpipilian para sa maliliit na proyekto at prototype.
Constructor Injection — pamantayan. Palaging gamitin ang Constructor Injection para sa mga sapilitang dependency. Ginagawa nitong tahasan ang mga dependency at ang bagay ay laging handa para sa trabaho. Setter Injection — para lamang sa mga opsyonal na dependency (hal., delegate o listener). Interface Injection — huwag gamitin, maliban kung sumusulat ka ng sarili mong DI library. Constructor Injection — ang tanging paraan upang matiyak na ang bagay ay nilikha sa isang wastong estado.
Isang klase — isang responsibilidad. Kung ang constructor ng isang klase ay nangangailangan ng 5+ parameter, malamang na nilalabag ng klase ang Single Responsibility Principle. Hatiin ang klase sa ilang may mas kaunting mga dependency. Senyales: kung sumusulat ka ng klase ng ServiceManager na may 6 na iba't ibang serbisyo — ito ay anti-pattern na God Object. Ilipat ang lohika ng negosyo sa Use Cases (Interactors), bawat isa ay may 1-2 dependency.
// ❌ Masama: 6 na dependency — 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
)
// ✅ Mabuti: Use Cases na may 1-2 dependency
class ProfileViewModel @Inject constructor(
private val loadProfileUseCase: LoadProfileUseCase,
private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)
Scope at lifecycle — piliin ang tamang scope para sa bawat dependency. Singleton: OkHttpClient, database, SharedPreferences. Scope ng feature: mga repositoryo, Use Cases (kung wala silang estado). Transient: Value Objects, DateFormatter, mga parser. Error sa scope — karaniwang problema: isang singleton na nag-iimbak ng estado ng screen ay humahantong sa mga tagas ng memorya. Sa Android Hilt, nilulutas ng @ActivityScoped ang problemang ito, sa Swinject — .container nang may pag-iingat.
Mga Madalas Itanong
Ang new ay lumilikha ng mahigpit na pagkakabit sa pagitan ng mga klase — hindi mo mapapalitan ang implementasyon nang hindi binabago ang code. Mahirap subukan: hindi ka makakapaglagay ng mock sa halip ng tunay na serbisyo. Nilalabag ang SRP: ang klase ay responsable para sa parehong lohika ng negosyo at paglikha ng mga dependency. Nilulutas ng DI ang mga problemang ito sa pamamagitan ng pag-inject ng mga dependency mula sa labas at pagtatrabaho sa mga abstraksyon.
Dagger Hilt — pamantayan mula sa Google, compile-time DI na may code generation, mas mahusay na performance at integrasyon sa Jetpack. Koin — runtime DI, mas simple sa configuration, ngunit mas mabagal at may mga error sa runtime. Piliin ang Hilt para sa mga production project. Ang Koin ay angkop para sa mga prototype at maliliit na application.
Hindi. Para sa iOS ay magagamit: Swinject (runtime, sikat), Needle (compile-time mula sa Uber), Dip (magaan), Weaver (batay sa Sourcery). Ang Apple ay hindi nagbibigay ng built-in na DI container, ngunit ang manu-manong injection sa pamamagitan ng init ay karaniwang kasanayan. Para sa SwiftUI, ang manu-manong DI sa pamamagitan ng Environment o @StateObject na walang panlabas na library ay kadalasang sapat.
Oo. Ang manu-manong injection sa pamamagitan ng constructor — ito ay DI nang walang framework. Service Locator — alternatibo nang walang framework. Ang mga pabrika at Factory Method — anyo rin ng DI. Ang framework (Dagger, Swinject) ay nag-automate ng nakagawiang rehistrasyon at paglutas ng mga dependency, ngunit para sa 10-20 klase, sapat na ang manu-manong DI.
Ang DI — ay isang teknik (pattern) na nagpapatupad ng prinsipyong Inversion of Control. Hindi tulad ng mga pattern ng GoF, ang DI ay walang mahigpit na istraktura ng 3-4 na klase. Ang DI ay isang paraan ng pag-oorganisa ng mga dependency, hindi isang pattern ng disenyo. Ang mga DI container (Dagger, Swinject) ay mga framework na nag-automate sa teknik na ito.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din