Service Locator — ang diwa ng pattern, sentral na registry at DI

May-akda: IT Sectr Nai-publish: 2026-02-18 Oras ng pagbabasa: 9 min

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 — sentral na registry para sa pagkuha ng mga serbisyo
  • Alternatibo sa DI — mas madaling iimplementa, ngunit itinatago ang mga dependency mula sa client
  • Anti-pattern — maraming developer ang itinuturing na ang Service Locator ay anti-pattern dahil sa mga nakatagong dependency
  • Global na access — ang locator ay static na naa-access mula sa kahit saan, nang walang pagpasa sa pamamagitan ng constructor
  • Pag-test — mas mahirap kaysa sa DI: kailangan ang pag-configure ng global na locator para sa bawat test

Ano ang Service Locator: diwa at istruktura ng pattern

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).

KomponentResponsibilidadSwift/Kotlin
ServiceLocatorGlobal na access sa mga serbisyoclass ServiceLocator
RegistryLugar ng imbakan ng mga factory/instance[ObjectIdentifier: Any]
ServiceKonkretong implementasyonNetworkService()

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: global na registry ng mga serbisyo

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 }.

swift
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: tamad na pagrerehistro

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() na may delegate, na ginagawang mas malinis ang code. Pinapalitan ng Reified generics () ang ObjectIdentifier — ang uri ay nakukuha mula sa generic. Ang mga Kotlin-locator ay madalas gumagamit ng ConcurrentHashMap para sa thread-safety nang walang malinaw na mga lock.

kotlin
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.

Service Locator vs DI: paghahambing at kailan pumili ng alin

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.

KatangianService LocatorDependency Injection
Kitaan ng mga dependencyNakatago sa katawan ng mga methodMalinaw sa constructor
Pag-testPag-configure ng global na registryMock sa constructor
ModularityAng global na registry ay hindi modularMga module na may iba't ibang container
Pagiging kumplikadoSimpleng implementasyon, 50-100 linyaNangangailangan ng Dagger/Swinject
Time-to-marketMabilis na pagsisimulaPag-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 problema ng Service Locator at mga alternatibo

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.

swift
// 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

Ang Service Locator ba ay isang anti-pattern?

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.

Paano naiiba ang Service Locator sa DI-container?

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.

Kailan gagamitin ang Service Locator sa iOS?

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.

Paano maiiwasan ang race condition 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.

Ang Service Locator ba ay singleton?

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

  • Service Locator — sentral na registry para sa pagkuha ng mga serbisyo sa pamamagitan ng static na access
  • Mga nakatagong dependency — pangunahing kapintasan: hindi nakikita ang mga dependency sa signature ng class
  • Pag-test — mas mahirap kaysa sa DI dahil sa global na estado at pangangailangan ng pag-reset
  • Swift/Kotlin — implementasyon sa pamamagitan ng thread-safe na diksyunaryo at reified generics
  • Alternatibo — DI (Dagger Hilt, Swinject) — pamantayan para sa mga production-project
  • Makatwiran — sa mga prototype, para sa mga global na serbisyo at legacy-project

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.

Pag-usapan ang proyekto

Basahin din