介绍

依赖注入一直是争论的热点话题。在Wayfair,我们都选择了更简单的方法:传递参数对进入初始化。虽然这种方法很容易遵循,但当应用程序开始成长为一个复杂的机器时,可伸缩性就成了一个问题。

我们的SonarQube举了一些例子:

(看看我们为其中一个类创建的庞大依赖包,我的头都晕了,我想我需要一些茶!)

类依赖bundle{…init (dataBundle: SomeDataClass ?, someService: WebService, displayManager: SomeDisplayManager?= nil, moreService: SomeService = Dispatcher.currentAppTarget(). somemanagersomeService displayTypeManager: SomeOtherDisplayManager吗?= nil, someCustomGraphics:图形?= nil, optionManager: SomeOptionManager?= nil, somePaymentManager: PayManagerProtocol?= PaymentManager。AddToCarProcessable = addToCartManager。sharedInstance trackingDelegate: SomeTrackableDelegate吗?= nil, featureTogglesRepository: FeatureToggleRepository? = nil, displayType: DisplayType = .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? = nil, productCollectionDelegate: ProductCollectionDelegate? = nil)

问题

当然,我们并不是在讨论初始化参数,而是应该重新考虑传递参数单例围绕它!

在上面的例子中,我们定义了DependencyBundle和它的使用SomeContainerSomeContainer需要DependencyBundle但实际上并没有使用这些依赖关系中的大部分(如果使用了,那么您会遇到另一个问题,即某些类太强大了!)这些依赖项直接传递给子堆栈。

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

  1. 试图使用任何类SomeContainer需要提供大量的单例列表,这个负担级联起来调用层次突然之间,即使是那些远离SomeContainer需要依赖它不使用。
  2. 可重用性几乎为零作为所有的子类SomeContainer紧密耦合DependencyBundle现在重用任何子类都是一团糟SomeContainer如果你没有实例DependencyBundle

的议案

在Wayfair上,我们热衷于构建自己的工具,所以我们提出了一个简单的2件系统来解决方案。

解析器接口:它接受依赖项的请求。它作为依赖和类之间的接口。

一组提供者:提供者是依赖关系的创建者。他们创造的依赖关系,并给它解析器的时候问。一些供应商甚至可以依靠其他供应商来创建一个强大的依赖关系图。

我们将创造我们自己的属性包装@ inject在声明属性时自动解析依赖项。

让我赚大钱

虽然有几种实现解析器/提供者协议的方法,但这是我们所设想的工作方式。

首先,让我们通过协议和属性包装器建立契约。

协议注射{///名称来查找注入实体静止无功名称:字符串{GET}}扩展注射{静止无功名称:字符串{回报“\(Self.self)”}} ///解析器的主要工作是解决请求到接入实体协议解析器{的init()FUNC设置()FUNC解析(名称:字符串) - >注射?} ///提供商的主要工作是提供实体解析器协议提供商{associatedtype T:注射FUNC提供() - > T'} @propertyWrapper STRUCT注入 {私人变种靶:T'公共变种解析:解析器?公共的init(解析器:解析器= SimpleArrayResolver.shared){self.resolver =解析器}公共变种wrappedValue:T'{不同诱变获得{//步骤1,如果目标==零{目标=解析器.resolve(名称:T.name)?作为?T】返回目标}突变集合{目标= NEWVALUE}}}

现在让我们实现一个简单的提供程序和一个解析器。这里我们将简化一些事情作为例子:

最后一类DisplayManagerProvider:提供商{typealias T = DISPLAYMANAGER FUNC提供() - > T'{// DisplayManager.shared步骤3}}最终类SimpleDisplayManager:DISPLAYMANAGER,注射{静态无功共享= DISPLAYMANAGER()}最终类SimpleArrayResolver:解析器{静态让共享= SimpleArrayResolver()私有VAR注射= [注射]()///在这个简单的例子。这个列表是硬编码的,但是我们能够并且应该动态加载当模块负载私人让供应商= [DisplayManagerProvider()]的init(){设置()} FUNC设置(){注射= providers.map {$ 0.provide()} .compactMap {$ 0}} FUNC解析(名称:字符串?) - >注射?{injectables.first(其中:{式(的:$ 0)。名称==名称})//步骤2}}

所以一切是如何工作的呢?让我们来看看它通过一个例子:

// MARK:注入的属性@Injected VAR DISPLAYMANAGER:DISPLAYMANAGER?
  1. 房地产包装@ inject有一个可变的getter,以便任何时候这个变量被访问,它检查是否这个属性如果这样继续前进,并要求根据名称值值默认的解析器实现
  2. 解析器接受名称并查看提供者,以确定是否提供了与相同名称匹配的任何实体,该实体也可能被缓存在解析器中,这取决于解析器是如何实现的。
  3. 该供应商提供了一个实体,它的解析器检索并把它给请求它的属性。
  4. 利润!被请求的类不知道属性是如何出现的,它也不需要知道。它只知道它已经准备好了!

别急,还有更多!(测试)

测试也更清洁和更容易。提供者/解析器交互给开发商一个机会来模拟一次单身,而不是在嘲笑通过从类初始化一遍又一遍。例如,可以通过与提供实体共享相同的名称模拟供应商更换供应商嘲笑一下子所有的单身人士

注意:

静止无功NAME = “\(DisplayerManager.self)”
///这可以在NSPrincipalClass类中完成,也可以在setup()方法simplearrayresolv .shared中的每个测试套件中完成。MockDisplayManagerProvider: Provider {typealias T = DisplayManager func provide() -> T?{MockDisplayManager。{static var name = "\(DisplayerManager.self)" static var shared = MockDisplayManager()}

最终的想法

尽管我们的解决方案相当简单,但实际上,当我们开始处理不容易获得的数据时,事情可能会更复杂一些。幸运的是,这种解决方案消除了将异步任务从调用者中转移到自己专用的提供程序类中的负担,因此我们仍然认为这是一个胜利!

这就是所有的人,这远远不是一个完整的解决方案,但希望可以为支持复杂的依赖需求奠定基础!我们很期待这将引领我们走向何方!

谢谢你的文章Swift 5.1将依赖注入提升到下一个层次通过麦克朗获取有关属性包装器的灵感和用法。