Service Locator pattern in Swift

删除回忆录丶 提交于 2019-12-05 04:43:39

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.

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