Service Locator — podstata vzoru, centrální registr a DI

Autor: IT Sectr Publikováno: 2026-02-18 Doba čtení: 9 min

Service Locator — architektonický vzor poskytující centrální registr (registry) služeb. Klientský kód požaduje službu přes statický lokátor, aniž by ji vytvářel přímo nebo přijímal přes konstruktor. Service Locator je často považován za alternativu k Dependency Injection: je jednodušší na implementaci, ale skrývá závislosti a ztěžuje testování. Vzor je implementován přes globální třídu Singleton s registrací a rozlišením služeb. Více — v srovnání DI a Service Locator od Martina Fowlera.

Hlavní

  • Service Locator — centrální registr (registry) pro získávání služeb
  • Alternativa k DI — jednodušší na implementaci, ale skrývá závislosti před klientem
  • Antivzor — mnoho vývojářů považuje Service Locator za antivzor kvůli skrytým závislostem
  • Globální přístup — lokátor je dostupný staticky odkudkoli, bez předávání přes konstruktor
  • Testování — obtížnější než DI: vyžaduje konfiguraci globálního lokátoru pro každý test

Co je Service Locator: podstata a struktura vzoru

Service Locator — vzor centralizující vytváření a poskytování služeb. V základu je třída Singleton (Locator), která obsahuje registr (Registry) služeb: slovník, kde klíčem je typ (nebo identifikátor) služby a hodnotou konkrétní implementace. Klientský kód volá ServiceLocator.resolve(ServiceProtocol.self) a obdrží hotovou instanci. Vzor nepředepisuje, jak je služba vytvářena — továrna, DI kontejner nebo new uvnitř lokátoru.

Struktura vzoru — Registry (registr typu [String: Any]), Locator (statická třída s register a resolve), Service (registrovatelná služba). Registr může ukládat továrny (closure/lambda pro vytvoření objektu) nebo hotové instance. Lokátor může být globální (jeden na aplikaci) nebo s rozsahem (na feature/modul). Rozlišení služby je vyhledávání ve slovníku podle typu. Swift a Kotlin používají typ jako klíč přes metatypy: ObjectIdentifier(ServiceProtocol.self).

KomponentaOdpovědnostSwift/Kotlin
ServiceLocatorGlobální přístup ke službámclass ServiceLocator
RegistryÚložiště továren/instancí[ObjectIdentifier: Any]
ServiceKonkrétní implementaceNetworkService()

Historie vzoru — Service Locator byl popsán v knize Java Patterns (1998) a později v Core J2EE Patterns (2001). Martin Fowler v článku z roku 2004 srovnává Service Locator s DI a poznamenává, že Service Locator je „jednodušší alternativa, ale horší pro testování“. V mobilním vývoji byl Service Locator používán v raných Android projektech a iOS aplikacích před příchodem Dagger a Swinject. Nyní se vzor vyskytuje častěji v legacy projektech a prototypech.

Service Locator ve Swift: globální registr služeb

Service Locator ve Swift — implementace přes statické vlastnosti a thread-safe slovník. Používá se registr s ObjectIdentifier(Protocol.self) jako klíčem a továrnami (()->Any) jako hodnotami. Líná inicializace (lazy var) — standardní praxe: služba je vytvořena při prvním požadavku. Swift vyžaduje explicitní převod typu při 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()
    }
}

// Registrace
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }

// Použití
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)

Správa rozsahu — lokátor může ukládat továrny (transient — pokaždé nový objekt) nebo hotové instance (singleton). Pro továrny je registrován closure, který je volán při každém resolve. Pro singleton — closure se zachycením vytvořené instance. Přidání rozsahu: .transient, .singleton, .weak (slabá reference — objekt žije dokud někdo drží referenci). Weak rozsah je vhodný pro UIKit ViewController, aby se předešlo únikům paměti při pop/dismiss.

Service Locator v Kotlin: líná registrace

Service Locator v Kotlin — kompaktní implementace přes object (Singleton) s inline reified funkcemi pro typovou bezpečnost. Kotlin umožňuje vytvoření stručného lokátoru: val service by locator<ServiceProtocol>() s delegátem, což činí kód čistším. Reified generics (<reified T>) nahrazují ObjectIdentifier — typ je získán z generika. Kotlinové lokátory často používají ConcurrentHashMap pro thread-safety bez explicitních zámků.

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

// Registrace
ServiceLocator.register<ApiService> { RetrofitApiService() }

// Použití ve třídě
class UserRepository {
    private val api: ApiService = ServiceLocator.resolve()
    private val db: Database = ServiceLocator.resolve()
}

// Delegát pro líné rozlišení
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()
// Použití: val api by locator()

Service Locator v Android — vyskytuje se ve starých projektech před příchodem Dagger. Jetpack Hilt a Koin vytlačily Service Locator v Android komunitě. Lokátor však zůstává relevantní pro unit testy: jednoduchá imitace ServiceLocator s mock službami bez Hilt. Plus: není třeba čekat na kompilaci Dagger pro testy. Mínus: pokud zapomenete přepsat lokátor v testu, testy používají produkční služby.

Service Locator vs DI: srovnání a kdy co zvolit

Viditelnost závislostí — hlavní rozdíl. DI deklaruje závislosti explicitně: init(service: ServiceProtocol) — jakékoli IDE zobrazí závislosti třídy. Service Locator je skrývá: dependencies = ServiceLocator.resolve() — skryto uvnitř metody. S DI můžete okamžitě vidět všechny závislosti třídy, s Service Locator musíte číst celé tělo třídy. To činí kód na Service Locator méně předvídatelným: změna registru může rozbít jakoukoli třídu používající lokátor.

VlastnostService LocatorDependency Injection
Viditelnost závislostíSkryté v těle metodExplicitní v konstruktoru
TestováníKonfigurace globálního registruMock v konstruktoru
ModularitaGlobální registr — není modulárníModuly s různými kontejnery
SložitostJednoduchá implementace, 50-100 řádkůVyžaduje Dagger/Swinject
Time-to-marketRychlý startKonfigurace kontejneru

Kdy je Service Locator oprávněný — prototypy a MVP (rychlý start bez konfigurace). Legacy projekty, kde nelze přidat DI framework (složitá kompilace, omezení linteru). Instrumentální knihovny (logování, hlášení chyb) — jsou stejně globální. Pro produkční aplikace s týmem od 3 vývojářů se doporučuje DI: explicitní závislosti snižují počet chyb při refaktorování a usnadňují uvedení nových vývojářů do projektu.

Problémy Service Locator a alternativy

Skryté závislosti — třídu používající ServiceLocator.resolve() uvnitř metody nelze staticky analyzovat. IDE nezobrazuje závislosti, kompilátor nekontroluje, zda je služba zaregistrována. Chyba „Service not registered“ nastává pouze za běhu. Refaktorování se stává riskantním: odstranění služby z registru může rozbít jakoukoli třídu v aplikaci. DI řeší tento problém pomocí kontrol v čase kompilace (Dagger) nebo explicitních konstruktorů.

Problém testování — každý test musí nakonfigurovat ServiceLocator.shared se všemi závislostmi. Po testu — resetovat (reset()) stav. Při paralelním spouštění testů vede globální stav ServiceLocator.shared k race condition: jeden test registruje mock, druhý — obdrží cizí mock. Řešení: lokátory s rozsahem (jeden na test) nebo ThreadLocal. DI řeší tento problém od základu: každý test si vytváří vlastní instanci s mock závislostmi.

swift
// Problém testování Service Locator
class LoginViewModelTests: XCTestCase {
    override func setUp() {
        super.setUp()
        // Konfigurace globálního registru pro test
        ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
    }

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

    func testLogin() {
        let viewModel = LoginViewModel() // používá ServiceLocator uvnitř
        // test...
    }
}

Alternativy k Service Locator — DI (Dagger, Swinject), Factory Method, Ambient Context. Factory Method — jednoduchý vzor bez globálního stavu: tovární třída vytváří služby a je předávána přes konstruktor. Ambient Context — thread-safe alternativa pro cross-cutting concerns (logování, autorizace). Nejlepší alternativa — Constructor Injection s ruční továrnou bez DI frameworku: explicitní vytváření závislostí v továrně s předáním přes konstruktor poskytuje přehlednost DI bez složitosti konfigurace Dagger/Swinject.

Často kladené otázky

Je Service Locator antivzor?

Mnoho vývojářů považuje Service Locator za antivzor, protože skrývá závislosti, ztěžuje testování a vytváří skryté vazby mezi třídami. Nicméně v prototypech, malých projektech a pro globální služby (logování, analýza) může být Service Locator oprávněný. Rozhodnutí závisí na kontextu: pro produkční aplikaci s týmem — DI, pro jednoho vývojáře na prototypu — Service Locator.

Čím se Service Locator liší od DI kontejneru?

DI kontejner (Dagger, Swinject) vkládá závislosti do objektu automaticky — objekt neví o existenci kontejneru. Service Locator — objekt sám požaduje závislosti z registru. DI kontejner dodržuje princip IoC, Service Locator — ho porušuje: objekt sám řídí získávání svých závislostí. DI kontejner pracuje před vytvořením objektu (přes konstruktor), Service Locator — na libovolném místě kódu.

Kdy použít Service Locator v iOS?

Service Locator je oprávněný v iOS pro: globální služby (Analytics, Logger, Crashlytics), prototypy, kde je konfigurace Swinject nadměrná, a pro unit testy velkých legacy modulů. Pro nové iOS projekty se doporučuje Swinject nebo ruční DI přes konstruktor. SwiftUI s Environment — také forma DI, která se vyhýbá Service Locator.

Jak se vyhnout race condition v Service Locator?

Použijte thread-safe kolekci (NSLock ve Swift, ConcurrentHashMap v Kotlin). Pro testy — ThreadLocal nebo lokátor s rozsahem. Alternativa: async-local storage — služby vázané na korutinu/aktor. Nejlepší řešení — vyhněte se Service Locator pro paralelní testy a použijte DI s explicitním vytvářením objektů pro každý test.

Je Service Locator singleton?

Obvykle je Service Locator implementován jako Singleton, ale není to povinné. Můžete vytvořit instanci lokátoru pro modul (feature-scoped locator) a předávat ji přes konstruktor. Feature-scoped lokátor řeší problém globálního stavu, ale neřeší problém skrytých závislostí. Takový vzor se nazývá Ambient Context nebo Scoped Locator.

Shrnutí

  • Service Locator — centrální registr pro získávání služeb přes statický přístup
  • Skryté závislosti — hlavní nevýhoda: závislosti nejsou viditelné v signatuře třídy
  • Testování — obtížnější než DI kvůli globálnímu stavu a nutnosti resetování
  • Swift/Kotlin — implementace přes thread-safe slovník a reified generics
  • Alternativa — DI (Dagger Hilt, Swinject) — standard pro produkční projekty
  • Oprávněný — v prototypech, pro globální služby a legacy projekty

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také