Service Locator — a minta lényege, központi jegyzék és DI

Szerző: IT Sectr Megjelenés: 2026-02-18 Olvasási idő: 9 perc

Service Locator — egy architekturális minta, amely központi szolgáltatásjegyzéket (registry) biztosít. A klienskód egy statikus lokátoron keresztül kér egy szolgáltatást, anélkül hogy közvetlenül létrehozná vagy konstruktoron keresztül kapná. A Service Locator-t gyakran a Dependency Injection alternatívájának tekintik: egyszerűbb implementálni, de elrejti a függőségeket és megnehezíti a tesztelést. A minta egy globális Singleton osztályon keresztül valósul meg szolgáltatások regisztrálásával és feloldásával. Bővebben — a Martin Fowler-féle DI és Service Locator összehasonlításban.

Főbb pontok

  • Service Locator — központi jegyzék (registry) szolgáltatások lekéréséhez
  • DI alternatíva — egyszerűbb implementálni, de elrejti a függőségeket a kliens elől
  • Antiminta — sok fejlesztő antimintának tartja a Service Locator-t a rejtett függőségek miatt
  • Globális hozzáférés — a lokátor statikusan elérhető bárhonnan, konstruktoron keresztüli átadás nélkül
  • Tesztelés — nehezebb, mint DI: a globális lokátor konfigurálása szükséges minden teszthez

Mi a Service Locator: a minta lényege és szerkezete

Service Locator — egy minta, amely központosítja a szolgáltatások létrehozását és biztosítását. Az alap egy Singleton osztály (Locator), amely szolgáltatások jegyzékét (Registry) tartalmazza: egy szótár, ahol a kulcs a szolgáltatás típusa (vagy azonosítója), az érték pedig a konkrét implementáció. A klienskód meghívja a ServiceLocator.resolve(ServiceProtocol.self)-t és egy kész példányt kap. A minta nem írja elő, hogyan jön létre a szolgáltatás — gyár, DI konténer vagy new a lokátoron belül.

A minta szerkezete — Registry ([String: Any] típusú jegyzék), Locator (statikus osztály register és resolve metódusokkal), Service (regisztrálható szolgáltatás). A jegyzék tárolhat gyárakat (closure/lambda objektum létrehozásához) vagy kész példányokat. A lokátor lehet globális (egy az alkalmazáshoz) vagy hatókörrel rendelkező (feature/modul szerint). A szolgáltatás feloldása egy keresés a szótárban típus alapján. A Swift és Kotlin a típust használja kulcsként metatípusokon keresztül: ObjectIdentifier(ServiceProtocol.self).

ÖsszetevőFelelősségSwift/Kotlin
ServiceLocatorGlobális hozzáférés szolgáltatásokhozclass ServiceLocator
RegistryGyárak/példányok tárolása[ObjectIdentifier: Any]
ServiceKonkrét implementációNetworkService()

A minta története — A Service Locator-t a Java Patterns (1998) könyv írta le, később a Core J2EE Patterns (2001) könyvben is megjelent. Martin Fowler 2004-es cikkében hasonlítja össze a Service Locator-t a DI-val, megjegyezve, hogy a Service Locator „egyszerűbb alternatíva, de rosszabb a teszteléshez“. A mobilfejlesztésben a Service Locator-t korai Android-projektekben és iOS-alkalmazásokban használták a Dagger és Swinject megjelenése előtt. Ma a minta gyakrabban fordul elő legacy projektekben és prototípusokban.

Service Locator Swift-ben: globális szolgáltatásjegyzék

Service Locator Swift-ben — implementáció statikus tulajdonságokon és thread-safe szótáron keresztül. Egy jegyzék kerül használatra ObjectIdentifier(Protocol.self) kulccsal és (()->Any) gyárakkal értékként. Lusta inicializálás (lazy var) — általános gyakorlat: a szolgáltatás az első kéréskor jön létre. A Swift explicit típuskonverziót igényel resolve-kor: 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()
    }
}

// Regisztráció
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }

// Használat
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)

Hatókör kezelés — a lokátor tárolhat gyárakat (transient — minden alkalommal új objektum) vagy kész példányokat (singleton). Gyárak esetén egy closure kerül regisztrálásra, amely minden resolve-kor meghívódik. Singleton esetén — closure a létrehozott példány rögzítésével. Hatókör hozzáadása: .transient, .singleton, .weak (gyenge referencia — az objektum addig él, amíg valaki tartja a referenciát). A weak hatókör kényelmes UIKit ViewController esetén a memóriaszivárgás elkerülésére pop/dismiss esetén.

Service Locator Kotlin-ban: lusta regisztráció

Service Locator Kotlin-ban — tömör implementáció object (Singleton) segítségével reified inline függvényekkel a típusbiztonságért. A Kotlin lehetővé teszi egy tömör lokátor létrehozását: val service by locator<ServiceProtocol>() delegálással, ami tisztábbá teszi a kódot. A Reified generics (<reified T>) helyettesíti az ObjectIdentifier-t — a típus a generikusból származik. A Kotlin lokátorok gyakran használnak ConcurrentHashMap-t a szálbiztonság érdekében explicit zárolások nélkül.

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()
    }
}

// Regisztráció
ServiceLocator.register<ApiService> { RetrofitApiService() }

// Használat osztályban
class UserRepository {
    private val api: ApiService = ServiceLocator.resolve()
    private val db: Database = ServiceLocator.resolve()
}

// Delegált lusta feloldáshoz
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()
// Használat: val api by locator()

Service Locator Android-ban — régi projektekben fordul elő a Dagger megjelenése előtt. A Jetpack Hilt és Koin kiszorította a Service Locator-t az Android közösségből. A lokátor azonban továbbra is releváns az egységtesztekhez: egyszerű ServiceLocator imitáció mock szolgáltatásokkal Hilt nélkül. Plusz: nem kell várni a Dagger fordítására a tesztekhez. Mínusz: ha elfelejti felülírni a lokátort a tesztben, a tesztek éles szolgáltatásokat használnak.

Service Locator vs DI: összehasonlítás és mikor mit válasszunk

Függőségek átláthatósága — a fő különbség. A DI explicit módon deklarálja a függőségeket: init(service: ServiceProtocol) — bármely IDE mutatja az osztály függőségeit. A Service Locator elrejti őket: dependencies = ServiceLocator.resolve() — rejtve a metóduson belül. DI esetén azonnal láthatja az osztály összes függőségét, Service Locator esetén el kell olvasnia az osztály teljes törzsét. Ez kevésbé kiszámíthatóvá teszi a Service Locator kódot: a jegyzék megváltoztatása bármely, a lokátort használó osztályt elronthat.

JellemzőService LocatorDependency Injection
Függőségek láthatóságaRejtve a metódusok törzsébenExplicit a konstruktorban
TesztelésGlobális jegyzék konfigurálásaMock a konstruktorban
ModularitásGlobális jegyzék — nem modulárisModulok különböző konténerekkel
KomplexitásEgyszerű implementáció, 50-100 sorDagger/Swinject szükséges
Time-to-marketGyors indulásKonténer konfigurálása

Mikor indokolt a Service Locator — prototípusok és MVP (gyors indulás konfiguráció nélkül). Legacy projektek, ahol nem lehet DI keretrendszert hozzáadni (bonyolult fordítás, lint korlátozások). Eszközkönyvtárak (naplózás, hibajelentés) — ezek úgyis globálisak. Éles alkalmazásokhoz 3 fejlesztőből álló csapattal a DI ajánlott: az explicit függőségek csökkentik a hibák számát refaktoráláskor és megkönnyítik az új fejlesztők bevezetését a projektbe.

A Service Locator problémái és alternatívák

Rejtett függőségek — egy osztály, amely ServiceLocator.resolve()-t használ egy metóduson belül, nem elemezhető statikusan. Az IDE nem mutatja a függőségeket, a fordító nem ellenőrzi, hogy a szolgáltatás regisztrálva van-e. A „Service not registered” hiba csak futásidőben jelentkezik. A refaktorálás veszélyessé válik: egy szolgáltatás eltávolítása a jegyzékből bármely osztályt elronthat az alkalmazásban. A DI ezt a problémát fordítási időben történő ellenőrzésekkel (Dagger) vagy explicit konstruktorokkal oldja meg.

Tesztelési probléma — minden tesztnek konfigurálnia kell a ServiceLocator.shared-et az összes függőséggel. A teszt után — vissza kell állítani (reset()) az állapotot. A tesztek párhuzamos futtatásakor a ServiceLocator.shared globális állapota versenyhelyzethez (race condition) vezet: az egyik teszt regisztrál egy mock-ot, egy másik — egy másik mock-ját kapja. Megoldás: hatókörrel rendelkező lokátorok (egy tesztre egy) vagy ThreadLocal. A DI ezt a problémát gyökeresen oldja meg: minden teszt létrehozza a saját példányát mock függőségekkel.

swift
// A Service Locator tesztelésének problémája
class LoginViewModelTests: XCTestCase {
    override func setUp() {
        super.setUp()
        // Globális jegyzék konfigurálása a teszthez
        ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
    }

    override func tearDown() {
        ServiceLocator.shared.reset()
        super.tearDown()
    }

    func testLogin() {
        let viewModel = LoginViewModel() // ServiceLocator-t használ belül
        // teszt...
    }
}

A Service Locator alternatívái — DI (Dagger, Swinject), Factory Method, Ambient Context. Factory Method — egyszerű minta globális állapot nélkül: a gyár osztály létrehozza a szolgáltatásokat és konstruktoron keresztül kerül átadásra. Ambient Context — thread-safe alternatíva keresztmetszeti aggályokhoz (naplózás, engedélyezés). A legjobb alternatíva — Constructor Injection kézi gyárral DI keretrendszer nélkül: a függőségek explicit létrehozása a gyárban konstruktoron keresztüli átadással megadja a DI átláthatóságát a Dagger/Swinject konfiguráció bonyolultsága nélkül.

Gyakran Ismételt Kérdések

Antiminta a Service Locator?

Sok fejlesztő antimintának tartja a Service Locator-t, mert elrejti a függőségeket, megnehezíti a tesztelést és rejtett kapcsolatokat hoz létre az osztályok között. Azonban prototípusokban, kis projektekben és globális szolgáltatások (naplózás, analitika) esetén a Service Locator indokolt lehet. A döntés a kontextustól függ: éles alkalmazáshoz csapattal — DI, egy fejlesztő prototípusához — Service Locator.

Miben különbözik a Service Locator a DI konténertől?

A DI konténer (Dagger, Swinject) automatikusan injektálja a függőségeket egy objektumba — az objektum nem tud a konténer létezéséről. Service Locator — az objektum maga kéri a függőségeket a jegyzékből. A DI konténer betartja az IoC elvet, a Service Locator — megsérti: az objektum maga kezeli a függőségei megszerzését. A DI konténer az objektum létrehozása előtt működik (konstruktoron keresztül), a Service Locator — a kód bármely pontján.

Mikor használjuk a Service Locator-t iOS-ben?

A Service Locator iOS-ben indokolt: globális szolgáltatásokhoz (Analytics, Logger, Crashlytics), prototípusokhoz ahol a Swinject konfigurálása túlzás, és nagy legacy modulok egységtesztjeihez. Új iOS projektekhez a Swinject vagy a kézi DI konstruktoron keresztül ajánlott. A SwiftUI Environment-nel — szintén a DI egy formája, amely elkerüli a Service Locator-t.

Hogyan kerüljük el a race condition-t a Service Locator-ban?

Használjon szálbiztos gyűjteményt (NSLock Swift-ben, ConcurrentHashMap Kotlin-ban). Tesztekhez — ThreadLocal vagy hatókörrel rendelkező lokátor. Alternatíva: async-local storage — szolgáltatások korutinhoz/aktorhoz kötve. A legjobb megoldás — kerülje a Service Locator-t párhuzamos tesztekhez, és használjon DI-t explicit objektumlétrehozással minden teszthez.

A Service Locator singleton?

Általában a Service Locator Singleton-ként van implementálva, de ez nem kötelező. Létrehozhat egy lokátor példányt egy modulhoz (feature-scoped locator) és átadhatja konstruktoron keresztül. A feature-scoped lokátor megoldja a globális állapot problémáját, de nem oldja meg a rejtett függőségek problémáját. Az ilyen mintát Ambient Context vagy Scoped Locator néven ismerik.

Összefoglalás

  • Service Locator — központi jegyzék szolgáltatások lekéréséhez statikus hozzáférésen keresztül
  • Rejtett függőségek — fő hátrány: a függőségek nem látszanak az osztály aláírásában
  • Tesztelés — nehezebb, mint DI a globális állapot és a visszaállítás szükségessége miatt
  • Swift/Kotlin — implementáció thread-safe szótárral és reified generics segítségével
  • Alternatíva — DI (Dagger Hilt, Swinject) — szabvány éles projektekhez
  • Indokolt — prototípusokban, globális szolgáltatásokhoz és legacy projektekben

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is