Resolver: Injection Strategies翻译
注入策略
使用 Resolver 执行依赖项注入的主要方法有五种:
名称和数量都来自于依赖反转,有关更深入的讨论,请参考 Martin Fowler.
Here I'll simply provide a brief description and an example of implementing each using Resolver.
在这里,我将简单地描述下每种策略,并提供 Resolver 实现对应策略的例子。
1. 接口注入
定义
第一种注入技术是为注入定义一个接口,并使用Swift扩展将该接口注入到类或对象中。
类:
class XYZViewModel {
lazy var fetcher: XYZFetching = getFetcher()
lazy var service: XYZService = getService()
func load() -> Data {
return fetcher.getData(service)
}
}
依赖注入的代码:
extension XYZViewModel: Resolving {
func getFetcher() -> XYZFetching { return resolver.resolve() }
func getService() -> XYZService { return resolver.resolve() }
}
func setupMyRegistrations {
register { XYZFetcher() as XYZFetching }
register { XYZService() }
}
请注意,仍需要在getFetcher() 和getService() 中调用resolve(),否则您将返回到紧密耦合依赖类并绕过解析注册系统。
优点:
- 轻盈。
- 对类隐藏依赖注入系统。
- 对于在初始化过程中没有访问权限的类,如UIViewController,非常有用。
缺点:
- 为每个需要注入的服务编写访问器函数。
2. 属性注入
定义
属性注入将其依赖项公开为属性,依赖项注入系统需要确保在调用任何方法之前都已设置好。
类:
class XYZViewModel {
var fetcher: XYZFetching!
var service: XYZService!
func load() -> Data {
return fetcher.getData(service)
}
}
依赖注入代码
func setupMyRegistrations {
register { XYZViewModel() }
.resolveProperties { (resolver, model) in
model.fetcher = resolver.optional() // Note property is an ImplicitlyUnwrappedOptional
model.service = resolver.optional() // Ditto
}
}
func setupMyRegistrations {
register { XYZFetcher() as XYZFetching }
register { XYZService() }
}
优点:
- 简洁。
- 也相当轻。
缺点:
- 将内部组件公开为公共变量。
- 很难确保一个对象得到了它所需要的一切来完成它的工作。
- 在注册时还有更多的工作要做。
3.构造器注入
定义
构造函数是Swift初始值设定项的Java术语,但其思想是相同的:通过初始化函数传递对象所需的所有依赖项。
类:
class XYZViewModel {
private var fetcher: XYZFetching
private var service: XYZService
init(fetcher: XYZFetching, service: XYZService) {
self.fetcher = fetcher
self.service = service
}
func load() -> Image {
let data = fetcher.getData(token)
return service.decompress(data)
}
}
依赖注入代码
func setupMyRegistrations {
register { XYZViewModel(fetcher: resolve(), service: resolve()) }
register { XYZFetcher() as XYZFetching }
register { XYZService() }
}
优点:
- 确保对象拥有完成其工作所需的一切,因为对象不能以其他方式构造。
- 将依赖项隐藏为私有或内部。
- 注册时需要的代码更少。
缺点:
- 要求对象具有具有所需所有参数的初始值设定项。
- 在对象初始值设定项中需要更多的样板代码来将参数转换为对象属性。
4.方法注入
定义
它不是一个模式,而是直接使用解析器,罗列出来以供选择。
就像它的名字一样,是将所需的对象注入到给定的方法中。
类:
class XYZViewModel {
func load(fetcher: XYZFetching, service: XYZFetching) -> Data {
return fetcher.getData(service)
}
}
依赖注入代码
你已经看过了。在load函数中,服务对象被传递到fetcher的getData方法中。
优点:
- 允许调用方动态配置方法的行为。
- 允许调用方构造自己的行为并将其传递到方法中。
缺点:
- 将这些行为公开给使用它的所有类。
注意
在Swift中,将闭包传递到方法中也可以被视为方法注入的一种形式。
5.服务定位器
定义
服务定位器基本上是定位对象所需的资源和依赖项的服务。
从技术上讲,服务定位器是它自己的设计模式,不同于依赖注入,但是解析器同时支持这两种模式,并且当支持视图控制器和初始化过程不在您控制范围内的其他类时,服务定位器模式特别有用。(请参考故事板。)
类:
class XYZViewModel {
var fetcher: XYZFetching = Resolver.resolve()
var service: XYZService = Resolver.resolve()
func load() -> Data {
return fetcher.getData(service)
}
}
依赖注入代码:
func setupMyRegistrations {
register { XYZFetcher() as XYZFetching }
register { XYZService() }
}
优点:
- 更少的代码。
- 对于在初始化过程中没有访问权限的类,如UIViewController,非常有用。
缺点:
- 将依赖注入系统公开给使用它的所有类。
6.注释
定义
Annotation uses comments or other metadata to indication that dependency injection is required. As of Swift 5.1, we can now perform annotation using Property Wrappers. (See Annotation.)
Annotation使用注释或其他元数据来指示需要进行依赖项注入。从swift5.1开始,我们现在可以使用属性包装器执行注释。(请参考注释。)
类:
class XYZViewModel {
@Injected var fetcher: XYZFetching
@Injected var service: XYZService
func load() -> Data {
return fetcher.getData(service)
}
}
依赖注入代码:
func setupMyRegistrations {
register { XYZFetcher() as XYZFetching }
register { XYZService() }
}
优点:
- 更少的代码。
- 隐藏注册系统的细节,可以很容易地创建一个注入的属性包装器来支持任何DI系统。
- 对于在初始化过程中没有访问权限的类,如UIViewController,非常有用。
缺点:
- 暴露了使用依赖注入系统的事实。
额外资源
这只是表面现象。要更深入地了解利弊,请参阅: Inversion of Control Containers and the Dependency Injection pattern ~ Martin Fowler