Service Locator — isang architectural pattern na nagbibigay ng sentral na registry ng mga serbisyo. Ang client code ay humihingi ng serbisyo sa pamamagitan ng static na locator, hindi ito direktang gumagawa o natatanggap ito sa pamamagitan ng constructor. Ang Service Locator ay madalas itinuturing na alternatibo sa Dependency Injection: mas madali itong iimplementa, ngunit itinatago nito ang mga dependency at pinapahirapan ang pag-test. Ang pattern ay naipapatupad sa pamamagitan ng global na Singleton-class na may pagrerehistro at pagresolba ng mga serbisyo. Para sa higit pang detalye — tingnan ang paghahambing ng DI at Service Locator ni Martin Fowler.
Mga Pangunahing Punto
Service Locator — isang pattern na nag-sentralisa ng paglikha at pagbibigay ng mga serbisyo. Sa puso nito — Singleton-class (Locator) na naglalaman ng Registry ng mga serbisyo: isang diksyunaryo kung saan ang key ay ang uri (o identifier) ng serbisyo, ang value ay ang konkretong implementasyon. Ang client code ay tumatawag ng ServiceLocator.resolve(ServiceProtocol.self) at nakakakuha ng handa na instance. Hindi itinatakda ng pattern kung paano ginagawa ang serbisyo — factory, DI-container o new sa loob ng locator.
Istruktura ng pattern — Registry (registry ng uri [String: Any]), Locator (static na class na may register at resolve), Service (serbisyong nirerehistro). Ang registry ay maaaring mag-imbak ng mga factory (closure/lambda para sa paglikha ng object) o handa nang mga instance. Ang locator ay maaaring global (isa bawat application) o scoped (ayon sa feature/module). Ang pagresolba ng serbisyo ay isang lookup sa diksyunaryo ayon sa uri. Gumagamit ang Swift at Kotlin ng uri bilang key sa pamamagitan ng mga metatype: ObjectIdentifier(ServiceProtocol.self).
| Komponent | Responsibilidad | Swift/Kotlin |
|---|---|---|
| ServiceLocator | Global na access sa mga serbisyo | class ServiceLocator |
| Registry | Lugar ng imbakan ng mga factory/instance | [ObjectIdentifier: Any] |
| Service | Konkretong implementasyon | NetworkService() |
Kasaysayan ng pattern — inilarawan ang Service Locator sa aklat na Java Patterns (1998) at kalaunan sa Core J2EE Patterns (2001). Sa artikulo ni Martin Fowler noong 2004, ikinukumpara ang Service Locator sa DI, binabanggit na ang Service Locator ay "mas simpleng alternatibo, ngunit mas masama para sa pag-test". Sa mobile development, ginamit ang Service Locator sa mga unang Android-project at iOS-app bago dumating ang Dagger at Swinject. Ngayon, mas madalas makikita ang pattern sa mga legacy-project at prototype.
Service Locator sa Swift — implementasyon sa pamamagitan ng mga static na property at thread-safe na diksyunaryo. Ginagamit ang registry na may ObjectIdentifier(Protocol.self) bilang key at mga factory (()->Any) bilang mga value. Ang lazy initialization (lazy var) — karaniwang praktika: ang serbisyo ay nilikha sa unang kahilingan. Kinakailangan ng Swift ang malinaw na type casting sa resolve: guard let service = locator.resolve(ServiceProtocol.self) else { return }.
final class ServiceLocator {
static let shared = ServiceLocator()
private var registry: [ObjectIdentifier: Any] = [:]
private let lock = NSLock()
func register<T>(_ type: T.Type, factory: @escaping () -> T) {
lock.lock()
registry[ObjectIdentifier(type)] = factory
lock.unlock()
}
func resolve<T>(_ type: T.Type) -> T {
lock.lock()
defer { lock.unlock() }
guard let factory = registry[ObjectIdentifier(type)] as? () -> T else {
fatalError("Service \(type) not registered")
}
return factory()
}
func reset() {
lock.lock()
registry.removeAll()
lock.unlock()
}
}
// Pagrerehistro
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }
// Paggamit
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)
Scope ng pamamahala — ang locator ay maaaring mag-imbak ng mga factory (transient — bagong object sa bawat pagkakataon) o handa nang mga instance (singleton). Para sa mga factory, nirerehistro ang closure na tinatawag sa bawat resolve. Para sa singleton — closure na may nakuhang instance na nilikha. Pagdaragdag ng scope: .transient, .singleton, .weak (mahinang reference — nabubuhay ang object habang may humahawak ng reference). Ang weak scope ay maginhawa para sa UIKit ViewController upang maiwasan ang mga leak sa pop/dismiss.
Service Locator sa Kotlin — siksik na implementasyon sa pamamagitan ng object (Singleton) na may mga inline na reified function para sa type-safety. Pinapayagan ng Kotlin ang paggawa ng maikling locator: val service by locator
object ServiceLocator {
private val registry = ConcurrentHashMap<Class<*>, () -> Any>()
inline fun <reified T: Any> register(noinline factory: () -> T) {
registry[T::class.java] = factory
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T: Any> resolve(): T {
val factory = registry[T::class.java]
?: throw IllegalStateException("Service ${T::class.simpleName} not registered")
return factory() as T
}
fun clear() {
registry.clear()
}
}
// Pagrerehistro
ServiceLocator.register<ApiService> { RetrofitApiService() }
// Paggamit sa class
class UserRepository {
private val api: ApiService = ServiceLocator.resolve()
private val db: Database = ServiceLocator.resolve()
}
// Delegate para sa tamad na pagresolba
class LocatorDelegate<reified T: Any> : Lazy<T> {
override val value: T get() = ServiceLocator.<T>resolve()
override fun isInitialized(): Boolean = true
}
inline fun <reified T: Any> locator(): Lazy<T> = LocatorDelegate()
// Paggamit: val api by locator<ApiService>()
Service Locator sa Android — makikita sa mga lumang project bago dumating ang Dagger. Pinalitan ng Jetpack Hilt at Koin ang Service Locator sa Android-community. Gayunpaman, nananatiling may kaugnayan ang locator para sa mga module test: simpleng stub ng ServiceLocator na may mga mock-service nang walang Hilt. Plus: hindi kailangang maghintay ng Dagger compilation para sa mga test. Minus: kung makalimutan mong i-override ang locator sa test, gagamitin ng mga test ang production-services.
Kalalantaran ng mga dependency — pangunahing pagkakaiba. Malinaw na idinedeklara ng DI ang mga dependency: init(service: ServiceProtocol) — ipinapakita ng anumang IDE ang mga dependency ng class. Itinatago sila ng Service Locator: dependencies = ServiceLocator.resolve() — nakatago sa loob ng method. Sa DI, makikita agad ang lahat ng dependency ng class; sa Service Locator, kailangang basahin ang buong katawan ng class. Ginagawa nitong hindi gaanong predictable ang code sa Service Locator: ang pagbabago sa registry ay maaaring makasira ng anumang class na gumagamit ng locator.
| Katangian | Service Locator | Dependency Injection |
|---|---|---|
| Kitaan ng mga dependency | Nakatago sa katawan ng mga method | Malinaw sa constructor |
| Pag-test | Pag-configure ng global na registry | Mock sa constructor |
| Modularity | Ang global na registry ay hindi modular | Mga module na may iba't ibang container |
| Pagiging kumplikado | Simpleng implementasyon, 50-100 linya | Nangangailangan ng Dagger/Swinject |
| Time-to-market | Mabilis na pagsisimula | Pag-configure ng container |
Kailan makatwiran ang Service Locator — mga prototype at MVP (mabilis na pagsisimula nang walang configuration). Mga legacy-project kung saan imposibleng magdagdag ng DI-framework (kumplikadong build, mga limitasyon ng linter). Mga instrumental na library (logging, crash-reporting) — global na rin sila. Para sa mga production-app na may team na 3 o higit pang developer, inirerekomenda ang DI: binabawasan ng malinaw na dependency ang bilang ng mga error sa refactoring at pinapadali ang pag-introduce ng mga bagong developer sa project.
Mga nakatagong dependency — ang class na gumagamit ng ServiceLocator.resolve() sa loob ng method ay hindi maaaring i-analyze nang static. Hindi ipinapakita ng IDE ang mga dependency, hindi tine-tsek ng compiler kung narehistro ang serbisyo. Ang error na "Service not registered" ay lumalabas lamang sa runtime. Nagiging mapanganib ang refactoring: ang pag-alis ng serbisyo mula sa registry ay maaaring makasira ng anumang class sa application. Nilulutas ng DI ang problemang ito sa pamamagitan ng compile-time checks (Dagger) o malinaw na mga constructor.
Problema sa pag-test — dapat i-configure ng bawat test ang ServiceLocator.shared kasama ang lahat ng dependency. Pagkatapos ng test — i-reset (reset()) ang estado. Sa parallel na pagpapatakbo ng mga test, ang global na estado ng ServiceLocator.shared ay nagdudulot ng race condition: ang isang test ay nirerehistro ang mock, ang isa pa — nakakakuha ng ibang mock. Solusyon: mga scoped locator (isa bawat test) o ThreadLocal. Nilulutas ng DI ang problemang ito mula sa simula: ang bawat test ay lumilikha ng sariling instance na may mga mock-dependency.
// Problema sa pag-test ng Service Locator
class LoginViewModelTests: XCTestCase {
override func setUp() {
super.setUp()
// Pag-configure ng global na registry para sa test
ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
}
override func tearDown() {
ServiceLocator.shared.reset()
super.tearDown()
}
func testLogin() {
let viewModel = LoginViewModel() // gumagamit ng ServiceLocator sa loob
// test...
}
}
Mga alternatibo sa Service Locator — DI (Dagger, Swinject), Factory Method, Ambient Context. Ang Factory Method — simpleng pattern na walang global na estado: ang factory class ay lumilikha ng mga serbisyo at ipinapasa sa pamamagitan ng constructor. Ang Ambient Context — thread-safe na alternatibo para sa mga cross-cutting concern (logging, authorization). Ang pinakamahusay na alternatibo — Constructor Injection na may manual na factory nang walang DI-framework: ang malinaw na paglikha ng mga dependency sa factory na may pagpasa sa pamamagitan ng constructor ay nagbibigay ng kitaan ng DI nang walang kumplikadong pag-configure ng Dagger/Swinject.
Mga Madalas Itanong
Itinuturing ng maraming developer na ang Service Locator ay anti-pattern dahil itinatago nito ang mga dependency, pinapahirapan ang pag-test at lumilikha ng mga nakatagong koneksyon sa pagitan ng mga class. Gayunpaman, sa mga prototype, maliliit na project at para sa mga global na serbisyo (logging, analytics) ang Service Locator ay maaaring makatwiran. Ang desisyon ay nakadepende sa konteksto: para sa production-app na may team — DI, para sa isang developer sa prototype — Service Locator.
Ang DI-container (Dagger, Swinject) ay awtomatikong nag-i-inject ng mga dependency sa object — hindi alam ng object ang pag-iral ng container. Ang Service Locator — ang object mismo ang humihingi ng mga dependency mula sa registry. Sinusunod ng DI-container ang prinsipyong IoC; nilalabag ito ng Service Locator: ang object mismo ang namamahala sa pagkuha ng mga dependency nito. Gumagana ang DI-container bago likhain ang object (sa pamamagitan ng constructor), ang Service Locator — saanman sa code.
Makatwiran ang Service Locator sa iOS para sa: mga global na serbisyo (Analytics, Logger, Crashlytics), mga prototype kung saan labis ang pag-configure ng Swinject, at para sa mga module test ng malalaking legacy-module. Para sa mga bagong iOS-project, inirerekomenda ang Swinject o manual na DI sa pamamagitan ng constructor. Ang SwiftUI na may Environment — isa ring anyo ng DI na umiiwas sa Service Locator.
Gumamit ng thread-safe na koleksyon (NSLock sa Swift, ConcurrentHashMap sa Kotlin). Para sa mga test — ThreadLocal o scoped locator. Alternatibo: async-local storage — ang mga serbisyo ay nakatali sa coroutine/actor. Ang pinakamahusay na solusyon — iwasan ang Service Locator para sa mga parallel test at gumamit ng DI na may malinaw na paglikha ng mga object para sa bawat test.
Karaniwang naipapatupad ang Service Locator bilang Singleton, ngunit hindi ito kinakailangan. Maaaring lumikha ng instance ng locator para sa isang module (feature-scoped locator) at ipasa ito sa pamamagitan ng constructor. Nilulutas ng feature-scoped locator ang problema ng global na estado, ngunit hindi nito nilulutas ang problema ng mga nakatagong dependency. Ang ganitong pattern ay tinatawag na Ambient Context o Scoped Locator.
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