Service Locator pattern in Swift

社会主义新天地 提交于 2019-12-07 00:10:28

问题


I'm interested in a flexible universal Service Locator design pattern implementation in Swift.

A naive approach may be as follows:

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

But it would be much better if the Service Locator will be able to handle any type of service without changing its code. How this can be achieved in Swift?

Note: the Service Locator is a pretty controversial design pattern (even called an anti-pattern sometimes), but please let's avoid this topic here.


回答1:


Actually, we can exploit Swift's type inference abilities to get a flexible universal and type-safe Service Locator. Here is the basic implementation (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
    }

}

It then can be used as follows:

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

This is already a usable implementation, but it would be also useful to allow lazy services initialization. Going one step further we'll have this (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
    }

}

It can be used in the following way:

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

Pretty neat, isn't it? And I think that using a Service Locator in conjunction with Dependency Injection lets avoid some cons of the former pattern.



来源:https://stackoverflow.com/questions/34406817/service-locator-pattern-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!