Service Locator — nümunənin mahiyyəti, mərkəzi reyestr və DI

Müəllif: IT Sectr Dərc olunub: 2026-02-18 Oxuma vaxtı: 9 dəq

Service Locator — xidmətlərin mərkəzi reyestrini (registry) təmin edən memarlıq nümunəsi. Müştəri kodu xidməti birbaşa yaratmadan və konstruktor vasitəsilə almadan statik lokator vasitəsilə sorğulayır. Service Locator tez-tez Dependency Injection alternativi kimi qəbul edilir: tətbiqi daha sadədir, lakin asılılıqları gizlədir və testi çətinləşdirir. Nümunə qlobal Singleton sinfi vasitəsilə xidmətlərin qeydiyyatı və həlli ilə həyata keçirilir. Ətraflı — Martin Fowler-dən DI və Service Locator müqayisəsi.

Əsas

  • Service Locator — xidmətləri əldə etmək üçün mərkəzi reyestr (registry)
  • DI alternativi — tətbiqi daha sadə, lakin müştəridən asılılıqları gizlədir
  • Antinümunə — bir çox tərtibatçı Service Locator-u gizli asılılıqlara görə antinümunə hesab edir
  • Qlobal giriş — lokator konstruktor vasitəsilə ötürülmədən istənilən yerdən statik olaraq əlçatandır
  • Test — DI-dan çətindir: hər test üçün qlobal lokatorun konfiqurasiyası tələb olunur

Service Locator nədir: nümunənin mahiyyəti və strukturu

Service Locator — xidmətlərin yaradılmasını və təqdim edilməsini mərkəzləşdirən nümunə. Əsasda xidmətlərin reyestrini (Registry) saxlayan Singleton sinfi (Locator) dayanır: açarın xidmətin tipi (və ya identifikatoru), dəyərin konkret tətbiq olduğu lüğət. Müştəri kodu ServiceLocator.resolve(ServiceProtocol.self) çağırır və hazır nümunə alır. Nümunə xidmətin necə yaradılacağını müəyyən etmir — fabrik, DI konteyner və ya lokator daxilində new.

Nümunənin strukturu — Registry ([String: Any] tipli reyestr), Locator (register və resolve ilə statik sinif), Service (qeydiyyatdan keçən xidmət). Reyestr fabrikləri (obyekt yaratmaq üçün closure/lambda) və ya hazır nümunələri saxlaya bilər. Lokator qlobal (tətbiq üçün bir) və ya əhatə dairəli (feature/modul üzrə) ola bilər. Xidmətin həlli — lüğətdə tip üzrə axtarış. Swift və Kotlin metatiplər vasitəsilə tipi açar kimi istifadə edir: ObjectIdentifier(ServiceProtocol.self).

KomponentMəsuliyyətSwift/Kotlin
ServiceLocatorXidmətlərə qlobal girişclass ServiceLocator
RegistryFabriklərin/nümunələrin anbarı[ObjectIdentifier: Any]
ServiceKonkret tətbiqNetworkService()

Nümunənin tarixi — Service Locator Java Patterns (1998) kitabında və daha sonra Core J2EE Patterns (2001) kitabında təsvir edilmişdir. Martin Fowler 2004-cü il məqaləsində Service Locator-u DI ilə müqayisə edərək qeyd edir ki, Service Locator „daha sadə alternativdir, lakin test üçün pisdir“. Mobil inkişafda Service Locator Dagger və Swinject meydana çıxmazdan əvvəl erkən Android layihələrində və iOS tətbiqlərində istifadə olunurdu. İndi nümunə daha çox legacy layihələrdə və prototiplərdə rast gəlinir.

Swift-də Service Locator: qlobal xidmət reyestri

Swift-də Service Locator — statik xüsusiyyətlər və thread-safe lüğət vasitəsilə tətbiq. Açar kimi ObjectIdentifier(Protocol.self) və dəyər kimi fabriklərlə (()->Any) reyestr istifadə olunur. Ləng inizializasiya (lazy var) — standart təcrübə: xidmət ilk sorğuda yaradılır. Swift resolve zamanı tipin açıq şəkildə çevrilməsini tələb edir: 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()
    }
}

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

// İstifadə
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)

Əhatə dairəsinin idarə edilməsi — lokator fabrikləri (transient — hər dəfə yeni obyekt) və ya hazır nümunələri (singleton) saxlaya bilər. Fabriklər üçün hər resolve çağırışında icra olunan closure qeydiyyatdan keçir. Singleton üçün — yaradılmış nümunəni tutan closure. Əhatə dairəsinin əlavə edilməsi: .transient, .singleton, .weak (zəif istinad — obyekt kimsə istinadı saxladıqca yaşayır). Weak əhatə dairəsi UIKit ViewController üçün əlverişlidir ki, pop/dismiss zamanı yaddaş sızmalarının qarşısı alınsın.

Kotlin-də Service Locator: ləng qeydiyyat

Kotlin-də Service Locator — tip təhlükəsizliyi üçün reified inline-funksiyaları olan object (Singleton) vasitəsilə yığcam tətbiq. Kotlin lakonik lokator yaratmağa imkan verir: val service by locator<ServiceProtocol>() delegatla, bu kodu daha təmiz edir. Reified generics (<reified T>) ObjectIdentifier-i əvəz edir — tip generikdən əldə edilir. Kotlin lokatorları tez-tez açıq bloklamalar olmadan thread-safe olmaq üçün ConcurrentHashMap istifadə edir.

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

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

// Sinifdə istifadə
class UserRepository {
    private val api: ApiService = ServiceLocator.resolve()
    private val db: Database = ServiceLocator.resolve()
}

// Ləng həll üçün deleqat
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()
// İstifadə: val api by locator()

Android-də Service Locator — Dagger meydana çıxmazdan əvvəl köhnə layihələrdə rast gəlinir. Jetpack Hilt və Koin Android icmasında Service Locator-u sıxışdırıb. Bununla belə, lokator modul testləri üçün aktual qalır: Hilt olmadan mock-xidmətlərlə sadə ServiceLocator imitasiyası. Müsbət tərəfi: testlər üçün Dagger kompilasiyasını gözləmək lazım deyil. Mənfi tərəfi: testdə lokatoru əvəz etməyi unutsanız, testlər istehsal xidmətlərindən istifadə edir.

Service Locator vs DI: müqayisə və nə vaxt nəyi seçmək

Asılılıqların aşkarlığı — əsas fərq. DI asılılıqları açıq şəkildə bəyan edir: init(service: ServiceProtocol) — istənilən IDE sinfin asılılıqlarını göstərir. Service Locator onları gizlədir: dependencies = ServiceLocator.resolve() — metod daxilində gizlidir. DI ilə sinfin bütün asılılıqlarını dərhal görmək olar, Service Locator ilə sinfin bütün gövdəsini oxumaq lazımdır. Bu, Service Locator kodunu daha az proqnozlaşdırıla bilən edir: reyestrdə dəyişiklik lokatordan istifadə edən istənilən sinfi poza bilər.

XüsusiyyətService LocatorDependency Injection
Asılılıqların görünməsiMetod gövdəsində gizlidirKonstruktorda açıqdır
TestQlobal reyestrin konfiqurasiyasıKonstruktorda mock
ModulluqQlobal reyestr — modul deyilMüxtəlif konteynerli modullar
MürəkkəblikSadə tətbiq, 50-100 sətirDagger/Swinject tələb edir
Time-to-marketSürətli başlanğıcKonteynerin konfiqurasiyası

Service Locator nə vaxt əsaslandırılır — prototiplər və MVP (konfiqurasiyasız sürətli başlanğıc). DI çərçivəsini əlavə etmək mümkün olmayan legacy layihələr (mürəkkəb kompilasiya, linter məhdudiyyətləri). Alət kitabxanaları (loglama, crash-reporting) — onlar onsuz da qlobaldır. 3 tərtibatçıdan ibarət komandası olan istehsal tətbiqləri üçün DI tövsiyə olunur: açıq asılılıqlar refaktorinq zamanı səhvlərin sayını azaldır və yeni tərtibatçıların layihəyə daxil olmasını asanlaşdırır.

Service Locator problemləri və alternativlər

Gizli asılılıqlar — metod daxilində ServiceLocator.resolve() istifadə edən sinif statik təhlil edilə bilməz. IDE asılılıqları göstərmir, kompilyator xidmətin qeydiyyatdan keçib-keçmədiyini yoxlamır. „Service not registered“ xətası yalnız icra zamanı baş verir. Refaktorinq təhlükəli olur: reyestrdən xidmətin silinməsi tətbiqdə istənilən sinifi poza bilər. DI bu problemi kompilasiya vaxtı yoxlamaları (Dagger) və ya açıq konstruktorlar vasitəsilə həll edir.

Test problemi — hər test bütün asılılıqlarla ServiceLocator.shared-i konfiqurasiya etməlidir. Testdən sonra — vəziyyəti sıfırlamaq (reset()). Testlərin paralel icrasında ServiceLocator.shared-in qlobal vəziyyəti race condition-a səbəb olur: bir test mock qeydiyyatdan keçirir, digəri — başqasının mock-nu alır. Həll: əhatə dairəli lokatorlar (test başına bir) və ya ThreadLocal. DI bu problemi kökündən həll edir: hər test öz nümunəsini mock-asılılıqlarla yaradır.

swift
// Service Locator test problemi
class LoginViewModelTests: XCTestCase {
    override func setUp() {
        super.setUp()
        // Test üçün qlobal reyestrin konfiqurasiyası
        ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
    }

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

    func testLogin() {
        let viewModel = LoginViewModel() // daxilində ServiceLocator istifadə edir
        // test...
    }
}

Service Locator alternativləri — DI (Dagger, Swinject), Factory Method, Ambient Context. Factory Method — qlobal vəziyyət olmadan sadə nümunə: fabrik sinfi xidmətlər yaradır və konstruktor vasitəsilə ötürülür. Ambient Context — cross-cutting concerns (loglama, autentifikasiya) üçün thread-safe alternativ. Ən yaxşı alternativ — DI çərçivəsi olmadan əl ilə Constructor Injection: fabrikdə asılılıqların açıq şəkildə yaradılması və konstruktor vasitəsilə ötürülməsi Dagger/Swinject konfiqurasiyasının mürəkkəbliyi olmadan DI-nin aydınlığını verir.

Tez-tez verilən suallar

Service Locator antinümunədir?

Bir çox tərtibatçı Service Locator-u antinümunə hesab edir, çünki o, asılılıqları gizlədir, testi çətinləşdirir və siniflər arasında gizli əlaqələr yaradır. Bununla belə, prototiplərdə, kiçik layihələrdə və qlobal xidmətlər (loglama, analitika) üçün Service Locator əsaslandırıla bilər. Qərar kontekstdən asılıdır: komandası olan istehsal tətbiqi üçün — DI, tək tərtibatçının prototipi üçün — Service Locator.

Service Locator DI konteynerindən nə ilə fərqlənir?

DI konteyneri (Dagger, Swinject) asılılıqları avtomatik olaraq obyektə yerləşdirir — obyekt konteynerin mövcudluğundan xəbərsizdir. Service Locator — obyekt özü asılılıqları reyestrdən sorğulayır. DI konteyneri IoC prinsipinə əməl edir, Service Locator — pozur: obyekt öz asılılıqlarının əldə edilməsini idarə edir. DI konteyneri obyekt yaradılmazdan əvvəl (konstruktor vasitəsilə) işləyir, Service Locator — kodun istənilən yerində.

iOS-da Service Locator nə vaxt istifadə olunmalıdır?

Service Locator iOS-da aşağıdakı hallarda əsaslandırılır: qlobal xidmətlər (Analytics, Logger, Crashlytics), Swinject konfiqurasiyasının həddindən artıq olduğu prototiplər və böyük legacy modullarının modul testləri üçün. Yeni iOS layihələri üçün Swinject və ya konstruktor vasitəsilə əl ilə DI tövsiyə olunur. Environment ilə SwiftUI — həmçinin Service Locator-dan qaçan DI formasıdır.

Service Locator-da race condition-dan necə qaçınmaq olar?

Thread-safe kolleksiyadan istifadə edin (Swift-də NSLock, Kotlin-də ConcurrentHashMap). Testlər üçün — ThreadLocal və ya əhatə dairəli lokator. Alternativ: async-local storage — xidmətlər korutina/aktorata bağlıdır. Ən yaxşı həll — paralel testlər üçün Service Locator-dan qaçmaq və hər test üçün obyektlərin açıq şəkildə yaradılması ilə DI istifadə etməkdir.

Service Locator singletondurmu?

Adətən Service Locator Singleton kimi tətbiq edilir, lakin bu məcburi deyil. Modul üçün lokator nümunəsi (feature-scoped locator) yaradıb onu konstruktor vasitəsilə ötürmək olar. Feature-scoped lokator qlobal vəziyyət problemini həll edir, lakin gizli asılılıqlar problemini həll etmir. Belə nümunə Ambient Context və ya Scoped Locator adlanır.

Nəticə

  • Service Locator — statik giriş vasitəsilə xidmətləri əldə etmək üçün mərkəzi reyestr
  • Gizli asılılıqlar — əsas çatışmazlıq: asılılıqlar sinfin imzasında görünmür
  • Test — qlobal vəziyyət və sıfırlama ehtiyacı səbəbindən DI-dan çətindir
  • Swift/Kotlin — thread-safe lüğət və reified generics vasitəsilə tətbiq
  • Alternativ — DI (Dagger Hilt, Swinject) — istehsal layihələri üçün standart
  • Əsaslandırılır — prototiplərdə, qlobal xidmətlər və legacy layihələrdə

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun