一、名词解析。
维基百科上的解释是依赖注入(Dependency Injection,简称DI)是实现控制反转(Inversion of Control,缩写为IoC)的一种用以降低代码耦合度的一种设计模式。
二、进入主题。
请先看下面的示例有什么问题没有
@interface Service : NSObject - (void)doSomeThing; @end @implementation Service - (void)doSomeThing { // TODO: } @end @interface Client : NSObject { Service *_service; } - (void)doTheWork; @end @implementation Client - (instancetype)init { self = [super init]; if (self) { _service = [Service new]; } return self; } - (void)doTheWork { NSLog(@"I'm do the work, and ask service do some subwork"); [_service doSomeThing]; } @end
咋一看,这样的代码看起来好像也没有什么问题:Client有一个Service成员,Client直接New一个Service成员,然后在工作时将一部分转包给Service去做。但是,在这种情况下,其实Client和Service就有一种hard-coded的依赖。
依赖注入,就是让Client不去显示的通过构造函数(objc's all & init or new)初始化成员变量的技术。
依赖注入有三种方式:
1. 构造函数注入:通过构造函数去提供所依赖的对象 (objc的init)
@implementation Client - (instancetype)initWithService:(Service *)sevice { self = [super init]; if (self) { _service = sevice; } return self; } @end
2. setter注入
@implementation Client - (void)setService:(Service *)service { _service = service; } @end
3. 接口注入
@protocol ServiceProtocol <NSObject> - (void)doSomeThing; @end @interface Service : NSObject <ServiceProtocol> @end @implementation Service - (void)doSomeThing { // TODO: } @end @interface Client : NSObject { id<ServiceProtocol> _service; } - (void)doTheWork; @end @implementation Client - (void)setService:(id<ServiceProtocol>)service { _service = service; } @end
我们可以看到,其实构造函数注入和setter注入也还是都依赖于具体的service类开。所以第三种接口注入其实才是比较好的方式。当然还应该加上一些对边界条件的检测。把三种注入方法接合起来。
@implementation Client - (void)setService:(id<ServiceProtocol>)service { _service = service; } - (instancetype)initWithService:(id<ServiceProtocol>)sevice { self = [super init]; if (self) { _service = sevice; } return self; } - (BOOL)validateState { if (!_service) { NSLog(@"Service cannot be nil"); // TODO: maybe throw a exception return NO; } return YES; } - (BOOL)doTheWork { BOOL done = NO; if ([self validateState]) { NSLog(@"I'm do the work, and ask service do some subwork"); [_service doSomeThing]; done = YES; } return done; } @end
有的人已经看不下去了:不就经常TM说的面向接口编程吗?yes, you are right.
还有人要说了,我们用第三方的库,用系统的库,有太多地方都是直接依赖的了,而且如果所有地方都要这样不直接依赖,那还不搞死人?
是的,面向对象也好,面向接口也好,都是有一个度的。做人做事何尝又不是呢。面向对象与面向过程不是互斥的。无论你怎么抽象、怎么面积对象,到具体的方法逻辑里面那一定是面向过程的。
一样的,通过setter注入也好,接口注入也好,最后真正决定要使用哪一个实现了service protocol的类型的时候,总得有一个地方去初始化吧?那么好了,在那个地方一定就是和这个具体的Service类型强依赖的。我们要做的只是把一些今后可能会变的地方,一些粒度大一些的模块这样实现便好。
关于依赖注入的库:Typhoon(objc, swift) Swinject
Reference: https://en.wikipedia.org/wiki/Dependency_injection