Patrón de localizador de service en Swift

Estoy interesado en una implementación de patrón de layout de localizador de services flexible en Swift.

Un enfoque ingenuo puede ser como sigue:

// Services declaration protocol S1 { func f1() -> String } protocol S2 { func f2() -> String } // Service Locator declaration // Type-safe and completely rigid. protocol ServiceLocator { var s1: S1? { get } var s2: S2? { get } } final class NaiveServiceLocator: ServiceLocator { var s1: S1? var s2: S2? } // 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 = NaiveServiceLocator() sl.s1 = S1Impl() sl.s2 = S2Impl() return sl }() // Test run print(sl.s1?.f1() ?? "S1 NOT FOUND") // S1 OK print(sl.s2?.f2() ?? "S2 NOT FOUND") // S2 OK 

Pero sería mucho mejor si el Localizador de Servicios pudiera manejar cualquier tipo de service sin cambiar su código. ¿Cómo se puede lograr esto en Swift?

Nota : el Localizador de services es un patrón de layout bastante polémico (incluso llamado anti-patrón algunas veces), pero por favor, evite este tema aquí.

En realidad, podemos explotar las capacidades de inferencia de tipo de Swift para get un localizador flexible y universal de services. Aquí está la implementación básica ( 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 } } 

Luego se puede usar de la siguiente manera:

 // 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 

Esto ya es una implementación utilizable, pero también sería útil permitir la initialization de services perezosos . Al ir un paso más allá, tendremos esta ( esencia ):

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

Se puede usar de la siguiente manera:

 // 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 

Bastante limpio, ¿no es así? Y creo que el uso de un localizador de services junto con la dependency injection permite evitar algunos inconvenientes del patrón anterior.