Service Locator, merkezi bir hizmet kaydı sağlayan mimari bir desendir. İstemci kodu, doğrudan oluşturmadan veya kurucu aracılığıyla almadan, statik bir konumlandırıcı aracılığıyla bir hizmet talep eder. Service Locator genellikle Dependency Injection'a bir alternatif olarak kabul edilir: uygulaması daha basittir, ancak bağımlılıkları gizler ve test etmeyi karmaşıklaştırır. Desen, hizmet kaydı ve çözümlemesi olan küresel bir Singleton sınıfı aracılığıyla uygulanır. Daha fazla detay — Martin Fowler'ın DI ve Service Locator karşılaştırmasında.
Önemli Noktalar
Service Locator, hizmetlerin oluşturulmasını ve sağlanmasını merkezileştiren bir desendir. Hizmetlerin bir kaydını içeren bir Singleton sınıfına (Locator) dayanır: anahtarın hizmet türü (veya tanımlayıcı) ve değerin somut uygulama olduğu bir sözlük. İstemci kodu ServiceLocator.resolve(ServiceProtocol.self) çağrısını yapar ve hazır bir örnek alır. Desen, hizmetin nasıl oluşturulacağını belirlemez — fabrika, DI kabı veya konumlandırıcı içinde new.
Desen yapısı — Kayıt ([String: Any] türünde sözlük), Konumlandırıcı (register ve resolve ile statik sınıf), Hizmet (kaydedilen hizmet). Kayıt, fabrikaları (nesne oluşturmak için closure/lambda) veya hazır örnekleri depolayabilir. Konumlandırıcı küresel (uygulama başına bir tane) veya kapsamlı (özellik/modül başına) olabilir. Hizmet çözümlemesi, türe göre sözlükte aramadır. Swift ve Kotlin, türü meta türler aracılığıyla anahtar olarak kullanır: ObjectIdentifier(ServiceProtocol.self).
| Bileşen | Sorumluluk | Swift/Kotlin |
|---|---|---|
| ServiceLocator | Hizmetlere küresel erişim | class ServiceLocator |
| Registry | Fabrika/örnek deposu | [ObjectIdentifier: Any] |
| Service | Somut uygulama | NetworkService() |
Desenin tarihi — Service Locator, Java Patterns (1998) kitabında ve daha sonra Core J2EE Patterns (2001)'de tanımlanmıştır. Martin Fowler'ın 2004 makalesi Service Locator'ı DI ile karşılaştırır ve Service Locator'ın "daha basit bir alternatif, ancak test için daha kötü" olduğunu belirtir. Mobil geliştirmede, Service Locator, Dagger ve Swinject ortaya çıkmadan önce erken Android projelerinde ve iOS uygulamalarında kullanıldı. Şimdi desen daha çok eski projelerde ve prototiplerde bulunur.
Swift'te Service Locator — statik özellikler ve iş parçacığı güvenli sözlük aracılığıyla uygulama. ObjectIdentifier(Protocol.self) anahtar ve fabrikalar (() -> Any) değer olarak kullanılan bir kayıt kullanılır. Tembel başlatma (lazy var) standart uygulamadır: hizmet ilk talep üzerine oluşturulur. Swift, resolve sırasında açık tür dönüşümü gerektirir: 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()
}
}
// Kayıt
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }
// Kullanım
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)
Kapsam yönetimi — konumlandırıcı fabrikaları (transient — her seferinde yeni nesne) veya hazır örnekleri (singleton) depolayabilir. Fabrikalar için, her resolve'da çağrılan bir closure kaydedilir. Singleton için, oluşturulan örneği yakalayan bir closure kullanılır. Kapsam ekleme: .transient, .singleton, .weak (zayıf referans — nesne, birisi referans tuttuğu sürece yaşar). UIKit UIViewController'lar için pop/dismiss sırasında sızıntıları önlemek için Weak kapsamı kullanışlıdır.
Kotlin'de Service Locator — tür güvenliği için inline reified işlevlerle object (Singleton) aracılığıyla kompakt bir uygulama. Kotlin, bir temsilci ile kısa bir konumlandırıcıya izin verir: val service by locator, kodu daha temiz hale getirir. Reified jenerikler () ObjectIdentifier yerine geçer — tür jenerikten elde edilir. Kotlin konumlandırıcıları genellikle açık kilitler olmadan iş parçacığı güvenliği için ConcurrentHashMap kullanır.
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()
}
}
// Kayıt
ServiceLocator.register<ApiService> { RetrofitApiService() }
// Sınıfta kullanım
class UserRepository {
private val api: ApiService = ServiceLocator.resolve()
private val db: Database = ServiceLocator.resolve()
}
// Tembel çözümleme için temsilci
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()
// Kullanım: val api by locator()
Android'de Service Locator — Dagger öncesi eski projelerde bulunur. Jetpack Hilt ve Koin, Android topluluğunda Service Locator'ın yerini almıştır. Ancak, konumlandırıcı birim testleri için geçerliliğini korur: Hilt olmadan mock hizmetlerle basit bir ServiceLocator saplaması. Artı: testler için Dagger derlemesini beklemeye gerek yok. Eksi: testte konumlandırıcıyı geçersiz kılmayı unutursanız, testler üretim hizmetlerini kullanır.
Bağımlılıkların açıklığı — temel fark. DI bağımlılıkları açıkça bildirir: init(service: ServiceProtocol) — herhangi bir IDE sınıf bağımlılıklarını gösterir. Service Locator onları gizler: dependencies = ServiceLocator.resolve() — yöntemin içinde gizli. DI ile tüm sınıf bağımlılıklarını hemen görebilirsiniz; Service Locator ile tüm sınıf gövdesini okumanız gerekir. Bu, Service Locator kodunu daha az tahmin edilebilir yapar: kaydı değiştirmek, konumlandırıcıyı kullanan herhangi bir sınıfı bozabilir.
| Özellik | Service Locator | Dependency Injection |
|---|---|---|
| Bağımlılık görünürlüğü | Yöntem gövdelerinde gizli | Kurucuda açık |
| Test | Küresel kaydı yapılandır | Kurucuda mock |
| Modülerlik | Küresel kayıt — modüler değil | Ayrı kaplarla modüller |
| Karmaşıklık | Basit uygulama, 50-100 satır | Dagger/Swinject gerekli |
| Pazara çıkış süresi | Hızlı başlangıç | Kapsayıcı kurulumu gerekli |
Service Locator ne zaman haklı — prototipler ve MVP'ler (yapılandırma olmadan hızlı başlangıç). DI çerçevesi eklenemeyen eski projeler (karmaşık derleme, lint kısıtlamaları). Enstrümantasyon kitaplıkları (günlükleme, çökme raporlama) — zaten küreseldir. 3+ geliştiricili ekip ile üretim uygulamaları için DI önerilir: açık bağımlılıklar yeniden düzenleme sırasındaki hata sayısını azaltır ve yeni geliştiricilerin işe alınmasını basitleştirir.
Gizli bağımlılıklar — bir yöntem içinde ServiceLocator.resolve() kullanan bir sınıf statik olarak analiz edilemez. IDE bağımlılıkları göstermez, derleyici hizmetin kayıtlı olup olmadığını kontrol etmez. "Service not registered" hatası yalnızca çalışma zamanında oluşur. Yeniden düzenleme tehlikeli hale gelir: kayıttan bir hizmeti kaldırmak, uygulamadaki herhangi bir sınıfı bozabilir. DI bu sorunu derleme zamanı kontrolleri (Dagger) veya açık kurucular aracılığıyla çözer.
Test sorunu — her test tüm bağımlılıklarla ServiceLocator.shared yapılandırmalıdır. Testten sonra — durumu sıfırlayın. Paralel test yürütme sırasında, ServiceLocator.shared küresel durumu yarış koşullarına yol açar: bir test bir mock kaydeder, diğer test başkasının mock'unu alır. Çözüm: kapsamlı konumlandırıcılar (test başına bir tane) veya ThreadLocal. DI bu sorunu en baştan çözer: her test mock bağımlılıklarla kendi örneğini oluşturur.
// Service Locator test sorunu
class LoginViewModelTests: XCTestCase {
override func setUp() {
super.setUp()
// Test için küresel kayıt kurulumu
ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
}
override func tearDown() {
ServiceLocator.shared.reset()
super.tearDown()
}
func testLogin() {
let viewModel = LoginViewModel() // dahili olarak ServiceLocator kullanır
// test...
}
}
Service Locator alternatifleri — DI (Dagger, Swinject), Factory Method, Ambient Context. Factory Method — küresel durum olmadan basit bir desen: bir fabrika sınıfı hizmetler oluşturur ve kurucu aracılığıyla iletilir. Ambient Context — çapraz kesen endişeler (günlükleme, yetkilendirme) için iş parçacığı güvenli bir alternatif. En iyi alternatif, DI çerçevesi olmadan manuel fabrika ile Constructor Injection'dır: kurucu iletimi ile bir fabrikada bağımlılıkların açıkça oluşturulması, Dagger/Swinject kurulumunun karmaşıklığı olmadan DI netliği sağlar.
Sıkça Sorulan Sorular
Birçok geliştirici, Service Locator'ı bağımlılıkları gizlediği, test etmeyi karmaşıklaştırdığı ve sınıflar arasında gizli bağlantı oluşturduğu için anti-desen olarak kabul eder. Ancak, prototiplerde, küçük projelerde ve küresel hizmetler (günlükleme, analitik) için Service Locator haklı olabilir. Karar bağlama bağlıdır: ekipli bir üretim uygulaması için — DI, prototipteki tek bir geliştirici için — Service Locator.
Bir DI kapsayıcısı (Dagger, Swinject) bağımlılıkları otomatik olarak bir nesneye enjekte eder — nesne kapsayıcının varlığından haberdar değildir. Service Locator — nesne bağımlılıkları kayıttan kendisi talep eder. Bir DI kapsayıcısı IoC ilkesini takip eder; Service Locator onu ihlal eder: nesne kendi bağımlılıklarının elde edilmesini yönetir. Bir DI kapsayıcısı nesne oluşturmadan önce (kurucu aracılığıyla) çalışır; Service Locator — kodun herhangi bir yerinde.
Service Locator iOS'te şunlar için haklıdır: küresel hizmetler (Analytics, Logger, Crashlytics), Swinject kurulumunun aşırı olduğu prototipler ve büyük eski modüllerin birim testleri. Yeni iOS projeleri için Swinject veya kurucu aracılığıyla manuel DI önerilir. @Environment ile SwiftUI — ayrıca Service Locator'dan kaçınan bir DI biçimidir.
İş parçacığı güvenli bir koleksiyon kullanın (Swift'te NSLock, Kotlin'de ConcurrentHashMap). Testler için — ThreadLocal veya kapsamlı konumlandırıcı. Alternatif: async-local depolama — hizmetler bir coroutine/actor'e bağlı. En iyi çözüm, paralel testler için Service Locator'dan kaçınmak ve her test için açık nesne oluşturma ile DI kullanmaktır.
Service Locator tipik olarak Singleton olarak uygulanır, ancak bu zorunlu değildir. Bir modül için bir konumlandırıcı örneği (özellik kapsamlı konumlandırıcı) oluşturabilir ve bunu kurucu aracılığıyla iletebilirsiniz. Özellik kapsamlı konumlandırıcı küresel durum sorununu çözer ancak gizli bağımlılıklar sorununu çözmez. Bu desen Ambient Context veya Scoped Locator olarak adlandırılır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.