【问题标题】:Service Locator pattern in SwiftSwift 中的服务定位器模式
【发布时间】:2016-03-28 04:26:15
【问题描述】:

我对 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 中实现这一点?

注意:Service Locator 是一个颇具争议的设计模式(有时甚至被称为反模式),但请在此避开这个话题。

【问题讨论】:

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


    【解决方案1】:

    实际上,我们可以利用 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
    

    很整洁,不是吗?而且我认为将服务定位器与依赖注入结合使用可以避免前一种模式的一些缺点。

    【讨论】:

      猜你喜欢
      • 2011-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2019-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多