如何将单例模式与依赖项注入结合使用?

时间:2019-04-02 21:03:29

标签: swift design-patterns dependency-injection singleton

我最近听说,使用依赖注入是“在当今软件开发界中使用单例的唯一社会上可接受的方式”。我不必立即就此声明的准确性进行辩论,因为它主要是基于观点的。我现在的目标是确切了解如何我可以将依赖注入与单例模式一起使用。

例如,在我最新的iOS应用中,我有一个Service层,其中保存我的 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()
    }

}

当然,上面的代码使用单例,但不使用依赖项注入。我知道,如果我想一般使用依赖注入,我会在 ViewController 中添加这样的内容:

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

TL; DR:

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

(2)您能向我解释一下这可以实现什么吗?

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

1 个答案:

答案 0 :(得分:4)

  1.   

    您能告诉我如何在Swift中的单例模式中正确使用依赖注入吗?

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

    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的初始化是由情节提要完成的,因此您无法通过初始化参数来增加依赖关系,而必须使用在对象初始化之后设置的存储属性。

  2.   

    您能告诉我这能达到什么目的吗?

    您的代码不再与ProductionFooService.shared耦合。结果,您可以引入FooService的不同实现,例如用于beta环境的一种实现,用于单元测试的模拟实现等。

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

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

    2. 没有真正的“单元”测试。每个测试都将测试一个代码单元,以及它传递的所有常见依赖关系。如果您要对这些依赖关系之一进行代码更改,那么它将破坏系统中的大多数单元测试,这将使得更难以准确确定失败的原因。通过分离依赖关系,您可以使用模拟对象,这些对象要做的最小工作足以支持单元测试,并确保每个测试仅在测试特定的代码单元,而不是其依赖的传递性依赖。

      < / li>
  3.   

    从现在开始在我的iOS项目中使用单例模式时,我应该始终使用DI吗?

    这是一个好习惯。当然,有些quicik-dirty项目您只是想快速移动而不会真正关心,但是会让您感到惊讶的是,这些所谓的quicik-dirty项目实际起飞了多少,并且付清一切费用。您只需要意识到自己何时会受到阻碍,就不必花一些额外的时间来消除体面因素。