swift - 如何结合依赖注入(inject)使用单例模式?

标签 swift design-patterns dependency-injection singleton

我最近听说使用依赖注入(inject)是“在当今软件开发世界中使用单例的唯一被社会接受的方式”。我现在不一定想争论这个声明的准确性,因为它主要是基于意见的。我现在的目标是了解如何将依赖注入(inject)与单例模式结合使用。

例如,在我最新的 iOS 应用程序中,我有一个服务层,用于保存我的 URLSession 代码。我将这一层创建为单例:

struct ServiceSingleton {

    private init()

    static let shared = ServiceSingleton()

    func fetchJSON() {
     // URLSession code
    }

}

然后我在我的 ViewController 中使用 shared,如下所示:

class ViewController: UIViewController() {

    override viewDidLoad() {
        super.viewDidLoad()

        fetchData()    

    }

    fileprivate func fetchData() {

        ServiceSingleton.shared.fetchJSON()
    }

}

当然,上面的代码使用了单例,但是没有使用依赖注入(inject)。我知道,如果我想在一般情况下使用依赖注入(inject),我会向 ViewController 添加类似的内容:

// Dependency Injection Constructor
override init(someProperty: SomePropertyType) {
    self.someProperty = someProperty
    super.init()
}

长话短说:

(1) 你能告诉我如何在 Swift 中正确使用依赖注入(inject)和单例模式吗?

(2) 你能给我解释一下这实现了什么吗?

(3) 从现在开始,当我在我的 iOS 项目中使用单例模式时,我是否应该总是使用 DI?

最佳答案

  1. Could you show me how to properly use dependency injection with the singleton pattern in Swift?

    不是直接访问 ServiceSingleton.shared,而是访问注入(inject)到对象中的实例变量,如果可能,通常在初始化程序中,否则作为可设置的属性,初始化后:

    protocol FooService {
        func doFooStuff()
    }
    
    class ProductionFooService: FooService {
    
        private init() {}
    
        static let shared = ProductionFooService()
    
        func doFooStuff() {
            print("real URLSession code goes here")
        }
    
    }
    
    struct MockFooService: FooService {
        func doFooStuff() {
            print("Doing fake foo stuff!")
        }
    }
    
    class FooUser {
        let fooService: FooService
    
        init(fooService: FooService) { // "initializer based" injection
            self.fooService = fooService
        }
    
        func useFoo() {
            fooService.doFooStuff() // Doesn't directly call ProductionFooService.shared.doFooStuff
        }
    }
    
    let isRunningInAUnitTest = false
    
    let fooUser: FooUser
    if !isRunningInAUnitTest {
        fooUser = FooUser(fooService: ProductionFooService.shared) // In a release build, this is used.
    }
    else {
        fooUser = FooUser(fooService: MockFooService()) // In a unit test, this is used. 
    }
    
    fooUser.useFoo()
    

    ViewController 的初始化通常由您的 Storyboard完成,因此您不能通过初始化参数引入依赖项,而必须改为使用在对象初始化后设置的存储属性。

  2. Could you explain to me what this achieves?

    您的代码不再耦合到 ProductionFooService.shared。因此,您可以引入 FooService 的不同实现,例如用于 beta 环境的实现、用于单元测试的模拟实现等。

    如果您的所有代码普遍直接使用您的产品依赖项,您将...

    1. 发现不可能在测试环境中实例化您的对象。您不希望您的单元测试、CI 测试环境、测试版环境等连接到产品数据库、服务和 API。

    2. 没有真正的“单元”测试。每个测试都将测试一个代码单元,以及它传递依赖的所有常见依赖项。如果您曾经对这些依赖项之一进行代码更改,它将破坏系统中的大部分单元测试,这使得更难准确确定失败的原因。通过解耦您的依赖关系,您可以使用模拟对象来执行支持单元测试所需的最低限度,并确保每个测试仅测试特定的代码单元,而不是它所依赖的传递依赖关系。

  3. Should I always use DI when I use the singleton pattern in my iOS projects from now on?

    捡起来是个好习惯。当然,有些快速而肮脏的项目你只是想快速行动而不会真正关心,但你会惊讶于这些所谓的快速和肮脏的项目中有多少真正起飞,并且付出代价。你只需要意识到什么时候你没有花一些额外的时间来摆脱你的体面而阻碍了自己。

关于swift - 如何结合依赖注入(inject)使用单例模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55483541/

相关文章:

c# - 如何在责任链中注入(inject)下一个处理程序的依赖?

swift - (SwiftLint)如果有主体,如何在 "\n"之后始终为 "{"(新行)编写规则(也许是自定义)?

asp.net - WCF+ Entity Framework 设计

language-agnostic - 设计模式会增加还是减少应用程序的复杂性?

java - 如何使用 Java 设计模式对一些 XPath 函数进行编程

c# - 依赖注入(inject)链接两个对象

c# - 依赖注入(inject)的 DbContext 始终为 null

ios - 是否需要在转义完成 block 后调用 return?

ios - 如何在 swift 中从 zipCode 获取坐标

ios - 如何将文本写入 swift3 iOS 中保存在服务器上的文件