【问题标题】:Better understanding of Dependency Injection - Resolving New Instances?更好地理解依赖注入 - 解析新实例?
【发布时间】:2021-05-10 09:06:15
【问题描述】:

我一直在从事一项需要我专注于依赖注入的工作。对于后人,我在 Swift/SwiftUI 中使用它,尽管我相信我对概念的理解比语言更固有。

我创建了一个依赖注入容器,可以用来注册和解析类型和组件。因此;

protocol MyContainerProtocol {
    func register<Component>(type: Component.Type, component: Any)
    func resolve<Component>(type: Component.Type) -> Component?
}

final class MyContainer: MyContainerProtocol {
    
    static let shared = DependencyContainer()
    private init() { }
    
    var components: [String: Any] = [:]
    
    func register<Component>(type: Component.Type, component: Any) {
        components["\(type)"] = component
    }
    
    func resolve<Component>(type: Component.Type) -> Component? {
        return components["\(type)"] as? Component
    }
}

这将在下面变得相关,但我的项目中有一个名为 VideoProcessor 的类;

class VideoProcessor: SomeProtocol {
    var codec: String
    var format: String

    init(codec: String, format: String) {
      self.codec = codec
      self.format = format
    }
}

在应用程序生命周期的早期,我正在注册组件。例如;

let container = DependencyContainer.shared
container.register(type: VideoProcessor.self, component: VideoProcessor(codec: "H264", format: "MP4"))
...
let processor = container.resolve(type: VideoProcessor.self)!

我的困惑:要求我解决一个类型的实例,而不必在注册时构造它。实际上,每次解析注册类型的新实例时,都会要求我解析它。在我看来,这意味着我的代码类似于:

let container = DependencyContainer.shared
container.register(type: VideoProcessor.self)
...
let processorA = container.resolve(type: VideoProcessor.self)!
processorA.codec = "H264"
processorA.format = "MP4"

let processorB = container.resolve(type: VideoProcessor.self)!
processorB.codec = "H265"
processorB.format = "MOV"

但是,VideoProcessor 有它自己的依赖项,导致我不确定如何注册类型。

我不确定我的问题是否存在于我的 Dependency Container 的构建方式、我的类的构建方式,或者我只是不理解所问的问题。即使查看像 Swinject 或 DIP 这样的流行 Swift 库,我也没有完全看到我的 Container 做错了什么(或者如果这是使用 Factory 方法的地方)。

【问题讨论】:

  • 如果您每次都需要创建一个新的实例,您可以注册一个(DependencyContainer) -&gt; Component函数,而不是一个已经构造好的对象。依赖容器部分在那里,因此您可以解决已注册的任何依赖项。绝对有必要从头开始构建它而不是使用库吗?我从未亲自使用过 Swinject 或 DIP,但它们可能已经解决了您遇到或将要自己构建的许多问题。
  • 感谢您的回复@Steven0351。如果我遵循您的评论,您是否建议在我的依赖容器中创建一个新函数,该函数在调用时会返回给定类的新实例化实例?这在实践中听起来很简单,但是根据我的 VideoProcessor 示例,当实例本身具有依赖项(而不是变量)时,我将如何处理返回实例?可以提供样品吗?
  • 我发布了一个答案,以便提供比 cmets 中更详细的示例。
  • 谢谢@Steven0351!我正在审查您的答案并努力消化知识并尝试一下。感谢您的帮助!

标签: swift dependency-injection inversion-of-control ioc-container


【解决方案1】:

您需要添加一个额外的注册函数。

protocol MyContainerProtocol {
  func register<Component>(type: Component.Type, component: Any)
  func register<Component>(type: Component.Type, builder: @escaping (MyContainerProtocol) -> Component)
  func resolve<Component>(type: Component.Type) -> Component?
}

final class MyContainer: MyContainerProtocol {
  
  static let shared = MyContainer()
  private init() { }
  
  var components: [String: Any] = [:]
  
  func register<Component>(type: Component.Type, component: Any) {
    components["\(type)"] = component
  }
  
  func register<Component>(type: Component.Type, builder: @escaping (MyContainerProtocol) -> Component) {
    components["\(type)"] = builder
  }
  
  func resolve<Component>(type: Component.Type) -> Component? {
    if let singleton = components["\(type)"] as? Component {
      return singleton
    }
    
    if let builder = components["\(type)"] as? (MyContainerProtocol) -> Component {
      return builder(self)
    }
    
    return nil
  }
}

然后在调用站点看起来像这样:

struct Animal {
  let type: String
  let id = UUID()
}

struct Person {
  let name: String
  let pet: Animal
  let id = UUID()
}

class ComplicatedNetworkStack {
  let id = UUID()
  /// so much stuff in here
}

MyContainer.shared.register(type: Animal.self) { _ in Animal(type: "Dog") }
MyContainer.shared.register(type: Person.self) { container in
  Person(
    name: "Joe Dirt",
    pet: container.resolve(type: Animal.self)!
  )
}

MyContainer.shared.register(type: ComplicatedNetworkStack.self, component: ComplicatedNetworkStack())

如果您要在操场上运行该代码并解析 Person 和 Animal 几次,您会看到 UUID 都不同,而 ComplicatedNetworkStack 的 id 是相同的。

【讨论】:

  • 感谢您的回复,@Steven0351。如果你愿意回答,我对“为什么”和“如何”有点好奇。我真的无法理解为什么有人会想要这个实现,因为我不禁认为依赖注入的原理是提供单一的事实来源,而不是允许创建多个事实同一类型。此外,原则上,我不确定 如何 这与我的示例不同,因为我可以在我的 VideoProcessor 类中添加一个 let id=UUID(),它似乎会做同样的事情?
  • let id = UUID() 只是一段代码,用于说明每次创建新的内容以及作为已构建对象传递的内容。就个人而言,我不在 iOS 应用程序中使用依赖注入。我对它的所有经验都来自服务器端的 Java 和 Kotlin,其中依赖注入是一种常见模式。但是,我只将依赖注入用于执行数据库调用、出站 http 请求等操作。当您去测试这些对象时,您可以将模拟注入到您的共享容器中
  • 谢谢@Steven0351。我也是这么感觉的;我没有在 iOS 中大量使用依赖注入,尤其是 SwiftUI 的出现,它使用环境对象来处理这些概念,而不是需要一个容器。这是重申我的知识的好方法,我感谢您的示例和详细信息。
猜你喜欢
  • 1970-01-01
  • 2015-10-08
  • 1970-01-01
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-13
  • 2016-12-28
相关资源
最近更新 更多