Swift中的服务定位器模式

时间:2015-12-22 00:47:35

标签: ios swift design-patterns swift2 service-locator

我对Swift中灵活的通用服务定位器设计模式实现感兴趣。

一种天真的方法可能如下:

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

但如果服务定位器能够在不更改其代码的情况下处理任何类型的服务,那么它将更好 。如何在Swift中实现这一目标?

注意:服务定位器是一个非常有争议的设计模式(有时甚至称为反模式),但请在此处避开此主题。

1 个答案:

答案 0 :(得分:12)

实际上,我们可以利用Swift的类型推断功能来获得灵活的通用和类型安全的服务定位器。这是基本实现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
    }

}

然后可以按如下方式使用:

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

这已经是一个可用的实现,但允许延迟服务初始化也很有用。更进一步,我们将(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
    }

}

可以按以下方式使用:

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

非常整洁,不是吗?我认为将服务定位器与依赖注入结合使用可以避免使用前一种模式。