介绍

依赖注入一直是争论的热点话题。在Wayfair,我们总是选择更简单的方法:将参数直接传递给初始化器。虽然这种方法很容易理解,可扩展性成为一个问题,一旦应用程序开始成长为一个复杂的机器。

我们SonarQube拿起一些例子:

(看看捆我们创造了我们班的一个让我目瞪口呆,我想我需要一些茶这种大规模的依赖!)

类DependencyBundle {...的init(dataBundle:SomeDataClass?someService:WebService的,DISPLAYMANAGER:SomeDisplayManager =零,moreService:SomeService = Dispatcher.currentAppTarget()someManager.someService,displayTypeManager:?SomeOtherDisplayManager =零,someCustomGraphics:显卡=零,optionManager:SomeOptionManager =零,somePaymentManager:PayManagerProtocol = PaymentManager.sharedInstance,addToCartManager:AddToCarProcessable = AddToCartManager.sharedInstance,trackingDelegate:SomeTrackableDelegate =零,featureTogglesRepository:FeatureToggleRepository =零,显示类型:显示类型= .DEFAULT,trackingManager:trackingManager= TrackingManager.shared,childViewProvider:SomeViewProviderProtocol = SomeCardView(),loggingManager:LoggingManager = LoggingManager.shared,customCartButtonAdapter:NavigationBarConfigurationDelegate = CustomCartButtonAdapter(),anotherConfigurationDelegate:NavigationBarConfigurationDelegate = CustomCartButtonAdapter(),STOREID:STOREID = SomeConfigManager.sharedInstance()storeConfig.storeID,notificationRegistrationManager:NotificationRegistrationManagerProtocol = NotificationRegistrationManager.sharedInstance,tracingClient:TracingClientProtocol = ApplicationDelegate.sharedTrace,statusMessageHandler:WFStatusMessageHandler = WFStatusMessageHandler.shared,replaceProduct:RegistryItem?=零,productCollectionDelegate:ProductCollectionDelegate?=无)

这个问题

虽然可以肯定,我们不是在争论沟初始化参数,我们应该重新考虑通过单身在它!

在我向您展示了上面的例子中,我们定义DependencyBundle它被SomeContainerSomeContainer要求DependencyBundle但实际上并未使用那些最依赖的(如果这样做,你有一定的阶级过于强大的另一个问题!)这些依赖简单地得到传承给孩子们堆。

什么是你可能会问的问题?那么考虑以下几点:

  1. 任何试图使用的类SomeContainer需要提供单身一个巨大的名单,这负担在调用层次结构中层叠而上而在很远的去除突然甚至班SomeContainer需要它不使用的依赖项。
  2. 重用能力降低到接近零因为所有的子类SomeContainer与…紧密相连DependencyBundle它现在是一个烂摊子重用任何子类SomeContainer如果你没有实例DependencyBundle

这个提议

在Wayfair我们都热衷于建立我们自己的工具,所以我们想出了一个简单的2件系统的建议的解决方案。

一个解析器接口:它接受的依赖关系的请求。它充当依赖项和类之间的接口。

一组供应商:商都依赖的创造者。它们创建依赖项,并在请求时将其交给解析器。有些提供者甚至可以依赖其他提供者来创建健壮的依赖关系图。

我们将创建自己的属性包装器@Injected自动解决依赖的属性声明时。

把钱拿出来

虽然有多种方法的解析器/供应商协议的实现,这是我们如何设想这应该工作。

首先,让我们通过协议和属性包装建立我们的合同的。

协议注入{///Name用于查找注入实体静态var Name: String {get}} extension注入{静态var Name: String {return "\(Self.self)"解析器的主要工作是解析访问实体的请求协议解析器{init() func setup() func resolve(name: String?) -> Injectable?{associatedtype T: Injectable func provide() -> T?} @propertyWrapper结构注入 {private var target: T?公共var解析器:解析器?公共init(解析器:解析器?= SimpleArrayResolver.shared) {self。public var wrappedValue: T?如果target == nil {target = resolver?解决(名字:T.name) ?T} return target}修改set {target = newValue}}

现在,让我们实现一个简单的提供者和解析器。在这里我们会简化一些东西为例:

最后一个类DisplayManagerProvider: Provider {typealias T = DisplayManager func provide() -> T?{DisplayManager。最后一个类SimpleArrayResolver: Resolver {static let shared = SimpleArrayResolver() private var injectables = [Injectable]() ///在这个简单示例中。这个列表是硬编码的,但是我们可以也应该在模块加载private时动态加载let providers = [DisplayManagerProvider()] init() {setup()} func setup() {injectables = providers。map {$0.provide()}.compactMap {$0} func解析(name: String?) ->可注入?{注射剂。first(where: {type(of: $0).name == name}) // step2}}

那么,这一切是如何运作的呢?让我们用一个例子来说明:

//标记:注入属性@注入var displayManager: displayManager ?
  1. 该属性包装@Injected具有突变吸气,使得任何时候,该变量被访问,它检查是否该属性,如果是这样,请继续并询问默认解析器实现基于名称值的值
  2. 该解析器将名字和长相的供应商,看看是否已经提供匹配的任何同名实体,这个实体也可以在解析器内缓存,这取决于解析器是如何实现的。
  3. 提供者提供一个实体,解析器将检索该实体并将其交给请求它的属性。
  4. 利润!请求的类不知道该怎么财产出现了,它并不真正需要。它只知道的是,它已经准备好去!

但是,等等,还有更多!(测试)

测试也更干净和更容易。提供程序/解析器的交互为开发人员提供了一次模拟单例的机会,而不是一次又一次地从类初始化器传入模拟。例如,可以通过使用提供共享相同名称的实体的模拟提供程序来替换提供程序,从而一次模拟所有单例

注意:

静态var name = "\(DisplayerManager.self)"
///这可以在NSPrincipalClass类来完成或可以是每个测试套件在设置()方法SimpleArrayResolver.shared.providers = [MockDisplayManagerProvider()]最终类MockDisplayManagerProvider:提供商{typealias T = DISPLAYMANAGER FUNC提供() - >T'{MockDisplayManager.shared}}最终类MockDisplayManager:DISPLAYMANAGER {静态无功名称= “\(DisplayerManager.self)” 静止无功共享= MockDisplayManager()}

最后的想法

虽然我们的解决方案是相当简单的,在现实中,事情可能有点多,当我们开始对付可能不是现成的数据复杂。幸运的是,该解决方案消除了负担,使异步任务关闭来电和到其自己的专用供应商类,所以我们仍然把那个叫胜利!

这是所有乡亲,这远非一个完整的解决方案,但希望将设置基础,以支持复杂的依赖性需求!我们很高兴地看到这导致我们下!

主要感谢文章在雨燕5.1注意到依赖注入到新的水平通过迈克尔长对财产的包装灵感和使用。