Service Locator एक आर्किटेक्चरल पैटर्न है जो सेवाओं की एक केंद्रीय रजिस्ट्री प्रदान करता है। क्लाइंट कोड एक स्टैटिक लोकेटर के माध्यम से सेवा का अनुरोध करता है, बिना इसे सीधे बनाए या कंस्ट्रक्टर के माध्यम से प्राप्त किए। Service Locator को अक्सर Dependency Injection के विकल्प के रूप में देखा जाता है: यह लागू करने में सरल है, लेकिन निर्भरताओं को छिपाता है और परीक्षण को जटिल बनाता है। पैटर्न एक वैश्विक Singleton वर्ग के माध्यम से सेवा पंजीकरण और समाधान के साथ कार्यान्वित किया जाता है। अधिक जानकारी — Martin Fowler द्वारा DI और Service Locator की तुलना में।
मुख्य बिंदु
Service Locator एक पैटर्न है जो सेवाओं के निर्माण और प्रावधान को केंद्रीकृत करता है। यह एक Singleton वर्ग (Locator) पर आधारित है जिसमें सेवाओं की एक रजिस्ट्री होती है: एक शब्दकोश जहाँ कुंजी सेवा प्रकार (या पहचानकर्ता) है और मान ठोस कार्यान्वयन है। क्लाइंट कोड ServiceLocator.resolve(ServiceProtocol.self) को कॉल करता है और एक तैयार इंस्टेंस प्राप्त करता है। पैटर्न यह निर्धारित नहीं करता कि सेवा कैसे बनाई जाती है — फैक्ट्री, DI-कंटेनर या लोकेटर के अंदर new।
पैटर्न संरचना — रजिस्ट्री ([String: Any] प्रकार का शब्दकोश), लोकेटर (register और resolve वाली स्टैटिक क्लास), सेवा (पंजीकृत की जा रही सेवा)। रजिस्ट्री फैक्ट्रियाँ (ऑब्जेक्ट बनाने के लिए closures/lambdas) या तैयार इंस्टेंस स्टोर कर सकती है। लोकेटर वैश्विक (एक प्रति एप्लिकेशन) या स्कोप्ड (प्रति फीचर/मॉड्यूल) हो सकता है। सेवा समाधान प्रकार के अनुसार शब्दकोश में खोज है। Swift और Kotlin मेटाटाइप के माध्यम से प्रकार को कुंजी के रूप में उपयोग करते हैं: ObjectIdentifier(ServiceProtocol.self)।
| घटक | जिम्मेदारी | Swift/Kotlin |
|---|---|---|
| ServiceLocator | सेवाओं तक वैश्विक पहुँच | class ServiceLocator |
| Registry | फैक्ट्रियों/इंस्टेंस का भंडारण | [ObjectIdentifier: Any] |
| Service | ठोस कार्यान्वयन | NetworkService() |
पैटर्न का इतिहास — Service Locator का वर्णन Java Patterns (1998) पुस्तक में और बाद में Core J2EE Patterns (2001) में किया गया। मार्टिन फाउलर के 2004 के लेख में Service Locator की तुलना DI से की गई है, जिसमें बताया गया है कि Service Locator एक "सरल विकल्प है, लेकिन परीक्षण के लिए बुरा है।" मोबाइल डेवलपमेंट में, Dagger और Swinject के आने से पहले प्रारंभिक Android प्रोजेक्ट्स और iOS ऐप्स में Service Locator का उपयोग किया जाता था। अब पैटर्न अक्सर लीगेसी प्रोजेक्ट्स और प्रोटोटाइप में पाया जाता है।
Swift में Service Locator — स्टैटिक प्रॉपर्टीज़ और थ्रेड-सेफ शब्दकोश के माध्यम से कार्यान्वयन। ObjectIdentifier(Protocol.self) को कुंजी और फैक्ट्रियों (() -> Any) को मान के रूप में उपयोग करने वाली रजिस्ट्री का उपयोग किया जाता है। आलसी आरंभीकरण (lazy var) मानक अभ्यास है: सेवा पहले अनुरोध पर बनाई जाती है। Swift को resolve के दौरान स्पष्ट प्रकार रूपांतरण की आवश्यकता होती है: 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()
}
}
// पंजीकरण
ServiceLocator.shared.register(NetworkServiceProtocol.self) { NetworkService() }
// उपयोग
let network = ServiceLocator.shared.resolve(NetworkServiceProtocol.self)
स्कोप प्रबंधन — लोकेटर फैक्ट्रियाँ (transient — हर बार नई वस्तु) या तैयार इंस्टेंस (singleton) स्टोर कर सकता है। फैक्ट्रियों के लिए, एक closure पंजीकृत किया जाता है जो प्रत्येक resolve पर कॉल किया जाता है। Singleton के लिए, एक closure बनाए गए इंस्टेंस को कैप्चर करता है। स्कोप जोड़ना: .transient, .singleton, .weak (कमज़ोर संदर्भ — वस्तु तब तक जीवित रहती है जब तक कोई संदर्भ रखता है)। UIKit UIViewControllers के लिए pop/dismiss पर लीक से बचने के लिए Weak स्कोप सुविधाजनक है।
Kotlin में Service Locator — प्रकार सुरक्षा के लिए inline reified फ़ंक्शन के साथ object (Singleton) के माध्यम से एक कॉम्पैक्ट कार्यान्वयन। Kotlin एक संक्षिप्त लोकेटर की अनुमति देता है: val service by locator एक डेलीगेट के साथ, कोड को साफ़ बनाता है। Reified generics () ObjectIdentifier को बदलते हैं — प्रकार जेनेरिक से प्राप्त होता है। Kotlin लोकेटर अक्सर स्पष्ट लॉक के बिना थ्रेड सुरक्षा के लिए ConcurrentHashMap का उपयोग करते हैं।
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()
}
}
// पंजीकरण
ServiceLocator.register<ApiService> { RetrofitApiService() }
// क्लास में उपयोग
class UserRepository {
private val api: ApiService = ServiceLocator.resolve()
private val db: Database = ServiceLocator.resolve()
}
// आलसी समाधान के लिए प्रतिनिधि
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()
// उपयोग: val api by locator()
Android में Service Locator — Dagger से पहले पुराने प्रोजेक्ट्स में पाया जाता है। Jetpack Hilt और Koin ने Android समुदाय में Service Locator को बदल दिया है। हालाँकि, यूनिट टेस्ट के लिए लोकेटर प्रासंगिक बना हुआ है: Hilt के बिना मॉक सेवाओं के साथ एक सरल ServiceLocator स्टब। लाभ: परीक्षणों के लिए Dagger संकलन की प्रतीक्षा करने की आवश्यकता नहीं। हानि: यदि आप परीक्षण में लोकेटर को ओवरराइड करना भूल जाते हैं, तो परीक्षण प्रोडक्शन सेवाओं का उपयोग करते हैं।
निर्भरताओं की स्पष्टता — मुख्य अंतर। DI निर्भरताओं को स्पष्ट रूप से घोषित करता है: init(service: ServiceProtocol) — कोई भी IDE वर्ग की निर्भरताएँ दिखाता है। Service Locator उन्हें छिपाता है: dependencies = ServiceLocator.resolve() — विधि के अंदर छिपा हुआ। DI के साथ, आप तुरंत सभी वर्ग निर्भरताएँ देख सकते हैं; Service Locator के साथ, आपको पूरे वर्ग बॉडी को पढ़ने की आवश्यकता है। यह Service Locator कोड को कम पूर्वानुमानित बनाता है: रजिस्ट्री बदलने से लोकेटर का उपयोग करने वाला कोई भी वर्ग टूट सकता है।
| विशेषता | Service Locator | Dependency Injection |
|---|---|---|
| निर्भरता दृश्यता | विधि निकायों में छिपी | कंस्ट्रक्टर में स्पष्ट |
| परीक्षण | वैश्विक रजिस्ट्री कॉन्फ़िगर करें | कंस्ट्रक्टर में मॉक |
| मॉड्यूलरिटी | वैश्विक रजिस्ट्री — मॉड्यूलर नहीं | अलग कंटेनर वाले मॉड्यूल |
| जटिलता | सरल कार्यान्वयन, 50-100 पंक्तियाँ | Dagger/Swinject आवश्यक |
| समय-से-बाज़ार | त्वरित शुरुआत | कंटेनर सेटअप आवश्यक |
Service Locator कब उचित है — प्रोटोटाइप और MVP (बिना कॉन्फ़िगरेशन के त्वरित शुरुआत)। लीगेसी प्रोजेक्ट जहाँ DI फ्रेमवर्क जोड़ना असंभव है (जटिल बिल्ड, लिंटर प्रतिबंध)। इंस्ट्रुमेंटेशन लाइब्रेरी (लॉगिंग, क्रैश रिपोर्टिंग) — वे पहले से ही वैश्विक हैं। 3+ डेवलपर की टीम वाले प्रोडक्शन अनुप्रयोगों के लिए, DI अनुशंसित है: स्पष्ट निर्भरताएँ रिफैक्टरिंग के दौरान त्रुटियों की संख्या कम करती हैं और नए डेवलपर को शामिल करना सरल बनाती हैं।
छिपी निर्भरताएँ — एक वर्ग जो विधि के अंदर ServiceLocator.resolve() का उपयोग करता है, स्थिर रूप से विश्लेषण नहीं किया जा सकता। IDE निर्भरताएँ नहीं दिखाता, कंपाइलर यह जाँच नहीं करता कि सेवा पंजीकृत है या नहीं। "Service not registered" त्रुटि केवल रनटाइम पर होती है। रिफैक्टरिंग खतरनाक हो जाती है: रजिस्ट्री से सेवा हटाने से एप्लिकेशन में कोई भी वर्ग टूट सकता है। DI इस समस्या को कंपाइल-टाइम जाँच (Dagger) या स्पष्ट कंस्ट्रक्टर के माध्यम से हल करता है।
परीक्षण समस्या — प्रत्येक परीक्षण को सभी निर्भरताओं के साथ ServiceLocator.shared कॉन्फ़िगर करना चाहिए। परीक्षण के बाद — स्थिति रीसेट करें। समानांतर परीक्षण निष्पादन के दौरान, ServiceLocator.shared की वैश्विक स्थिति रेस कंडीशन की ओर ले जाती है: एक परीक्षण मॉक पंजीकृत करता है, दूसरा परीक्षण किसी और का मॉक प्राप्त करता है। समाधान: स्कोप्ड लोकेटर (एक प्रति परीक्षण) या ThreadLocal। DI इस समस्या को शुरू से हल करता है: प्रत्येक परीक्षण मॉक निर्भरताओं के साथ अपना इंस्टेंस बनाता है।
// Service Locator परीक्षण की समस्या
class LoginViewModelTests: XCTestCase {
override func setUp() {
super.setUp()
// परीक्षण के लिए वैश्विक रजिस्ट्री सेटअप
ServiceLocator.shared.register(AuthServiceProtocol.self) { MockAuthService() }
}
override func tearDown() {
ServiceLocator.shared.reset()
super.tearDown()
}
func testLogin() {
let viewModel = LoginViewModel() // आंतरिक रूप से ServiceLocator का उपयोग करता है
// परीक्षण...
}
}
Service Locator के विकल्प — DI (Dagger, Swinject), Factory Method, Ambient Context। Factory Method — वैश्विक स्थिति के बिना एक सरल पैटर्न: एक फैक्ट्री वर्ग सेवाएँ बनाता है और कंस्ट्रक्टर के माध्यम से पास किया जाता है। Ambient Context — क्रॉस-कटिंग चिंताओं (लॉगिंग, प्राधिकरण) के लिए एक थ्रेड-सेफ विकल्प। सबसे अच्छा विकल्प DI फ्रेमवर्क के बिना मैन्युअल फैक्ट्री के साथ Constructor Injection है: कंस्ट्रक्टर पासिंग के साथ फैक्ट्री में निर्भरताओं का स्पष्ट निर्माण Dagger/Swinject सेटअप की जटिलता के बिना DI स्पष्टता देता है।
अक्सर पूछे जाने वाले प्रश्न
कई डेवलपर Service Locator को एंटी-पैटर्न मानते हैं क्योंकि यह निर्भरताओं को छिपाता है, परीक्षण को जटिल बनाता है, और वर्गों के बीच छिपा युग्मन बनाता है। हालाँकि, प्रोटोटाइप, छोटे प्रोजेक्ट और वैश्विक सेवाओं (लॉगिंग, एनालिटिक्स) के लिए, Service Locator उचित हो सकता है। निर्णय संदर्भ पर निर्भर करता है: टीम के साथ प्रोडक्शन एप्लिकेशन के लिए — DI, प्रोटोटाइप पर एकल डेवलपर के लिए — Service Locator।
DI कंटेनर (Dagger, Swinject) स्वचालित रूप से किसी वस्तु में निर्भरताएँ इंजेक्ट करता है — वस्तु कंटेनर के अस्तित्व के बारे में नहीं जानती। Service Locator — वस्तु स्वयं रजिस्ट्री से निर्भरताओं का अनुरोध करती है। DI कंटेनर IoC सिद्धांत का पालन करता है; Service Locator इसका उल्लंघन करता है: वस्तु अपनी निर्भरताएँ प्राप्त करने का प्रबंधन स्वयं करती है। DI कंटेनर वस्तु निर्माण से पहले (कंस्ट्रक्टर के माध्यम से) काम करता है, Service Locator — कोड में कहीं भी।
Service Locator iOS में इसके लिए उचित है: वैश्विक सेवाएँ (Analytics, Logger, Crashlytics), प्रोटोटाइप जहाँ Swinject सेटअप अत्यधिक है, और बड़े लीगेसी मॉड्यूल के यूनिट टेस्ट के लिए। नए iOS प्रोजेक्ट के लिए, Swinject या कंस्ट्रक्टर के माध्यम से मैन्युअल DI अनुशंसित है। SwiftUI @Environment के साथ — भी DI का एक रूप है, जो Service Locator से बचता है।
थ्रेड-सेफ कलेक्शन का उपयोग करें (Swift में NSLock, Kotlin में ConcurrentHashMap)। परीक्षणों के लिए — ThreadLocal या स्कोप्ड लोकेटर। विकल्प: async-local स्टोरेज — सेवाएँ coroutine/actor से बंधी होती हैं। सबसे अच्छा समाधान समानांतर परीक्षणों के लिए Service Locator से बचना और प्रत्येक परीक्षण के लिए स्पष्ट वस्तु निर्माण के साथ DI का उपयोग करना है।
Service Locator आमतौर पर Singleton के रूप में कार्यान्वित किया जाता है, लेकिन यह आवश्यक नहीं है। आप एक मॉड्यूल के लिए लोकेटर इंस्टेंस बना सकते हैं (फीचर-स्कोप्ड लोकेटर) और इसे कंस्ट्रक्टर के माध्यम से पास कर सकते हैं। फीचर-स्कोप्ड लोकेटर वैश्विक स्थिति समस्या को हल करता है लेकिन छिपी निर्भरताओं की समस्या को हल नहीं करता। इस पैटर्न को Ambient Context या Scoped Locator कहा जाता है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।