W rzeczywistości, możemy wykorzystać umiejętności wnioskowania typu Swift, aby uzyskać elastyczny uniwersalny i bezpieczny w obsłudze Service Locator. Oto podstawowym realizacja (gist):
protocol ServiceLocator {
func getService<T>() -> T?
}
final class BasicServiceLocator: ServiceLocator {
// Service registry
private lazy var reg: Dictionary<String, Any> = [:]
private func typeName(some: Any) -> String {
return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}
func addService<T>(service: T) {
let key = typeName(T)
reg[key] = service
//print("Service added: \(key)/\(typeName(service))")
}
func getService<T>() -> T? {
let key = typeName(T)
return reg[key] as? T
}
}
Następnie mogą być wykorzystane w następujący sposób:
// Services declaration
protocol S1 {
func f1() -> String
}
protocol S2 {
func f2() -> String
}
// Services imlementation
class S1Impl: S1 {
func f1() -> String {
return "S1 OK"
}
}
class S2Impl: S2 {
func f2() -> String {
return "S2 OK"
}
}
// Service Locator initialization
let sl: ServiceLocator = {
let sl = BasicServiceLocator()
sl.addService(S1Impl() as S1)
sl.addService(S2Impl() as S2)
return sl
}()
// Test run
let s1: S1? = sl.getService()
let s2: S2? = sl.getService()
print(s1?.f1() ?? "S1 NOT FOUND") // S1 OK
print(s2?.f2() ?? "S2 NOT FOUND") // S2 OK
To już użyteczny wdrożenia, ale byłoby również przydatne w celu umożliwienia leniwy Inicjalizacja usług. Idąc krok dalej będziemy mieć to (gist):
protocol ServiceLocator {
func getService<T>() -> T?
}
final class LazyServiceLocator: ServiceLocator {
/// Registry record
enum RegistryRec {
case Instance(Any)
case Recipe(() -> Any)
func unwrap() -> Any {
switch self {
case .Instance(let instance):
return instance
case .Recipe(let recipe):
return recipe()
}
}
}
/// Service registry
private lazy var reg: Dictionary<String, RegistryRec> = [:]
private func typeName(some: Any) -> String {
return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}
func addService<T>(recipe:() -> T) {
let key = typeName(T)
reg[key] = .Recipe(recipe)
}
func addService<T>(instance: T) {
let key = typeName(T)
reg[key] = .Instance(instance)
//print("Service added: \(key)/\(typeName(instance))")
}
func getService<T>() -> T? {
let key = typeName(T)
var instance: T? = nil
if let registryRec = reg[key] {
instance = registryRec.unwrap() as? T
// Replace the recipe with the produced instance if this is the case
switch registryRec {
case .Recipe:
if let instance = instance {
addService(instance)
}
default:
break
}
}
return instance
}
}
Może być stosowany w następujący sposób:
// Services declaration
protocol S1 {
func f1() -> String
}
protocol S2 {
func f2() -> String
}
// Services imlementation
class S1Impl: S1 {
let s2: S2
init(s2: S2) {
self.s2 = s2
}
func f1() -> String {
return "S1 OK"
}
}
class S2Impl: S2 {
func f2() -> String {
return "S2 OK"
}
}
// Service Locator initialization
let sl: ServiceLocator = {
let sl = LazyServiceLocator()
sl.addService { S1Impl(s2: sl.getService()!) as S1 }
sl.addService { S2Impl() as S2 }
return sl
}()
// Test run
let s1: S1? = sl.getService()
let s2: S2? = sl.getService()
//let s2_: S2? = sl.getService()
print(s1?.f1() ?? "S1 NOT FOUND") // S1 OK
print(s2?.f2() ?? "S2 NOT FOUND") // S2 OK
Całkiem miłe, prawda? I myślę, że używając Service Locator w połączeniu z Dependency Injection pozwala uniknąć niektórych wad poprzedniego wzorca.