一、名词解析。
维基百科上的解释是依赖注入(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