2015-12-22 15 views
5

Jestem zainteresowany elastycznym, uniwersalnym wzorcem wykonania usługi lokalizatora usług w Swift.Wzorzec lokalizatora usług w Swift

Naiwny podejście może być następująca:

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

Ale byłoby znacznie lepiej jeśli usługa Locator będzie w stanie obsłużyć każdy rodzaj usługi bez zmiany kodu. Jak można to osiągnąć w Swift?

Uwaga: Service Locator jest dość kontrowersyjny projekt wzoru (nawet nazwać anty-wzorzec czasami), ale proszę niech uniknąć ten temat tutaj.

Odpowiedz

6

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.

Powiązane problemy