一般更趋向于初始化注入,如果在初始化(构造函数)的时候没办法进行注入,才通过属性进行注入。在通过初始化注入的情况下,这些依赖可能仍然需要作为属性存在,但是这些属性应该被设置为只读(readonly)的。
为什么使用依赖注入
在这种情况,汽车当然是希望永远都没事,所以我们可能永远不需要灭火器。因为用到这个灭火器对象的几率很低,我们不想使得每一辆车创建得缓慢直接通过初始化方法创建它。或者,如果我们的汽车需要为多次车祸去恢复,这将需要创建多个灭火器。这种情况,我们可以使用一个工厂方法。
工厂方法是一个标准的Objective-C的block,它要求没有参数并且返回一个具体的实例对象。当一个对象依赖使用这个block创建时它不需要知道它具体是怎样被创建的。
下面,通过一个工厂,使用依赖注入创建一个灭火器。
如果一个对象不应该在其他对象内部进行配置,那就是用便利构造器(如+ [NSDictionary dictionary])。我们将把配置从我们的对象图中移出到我们普通的对象,分开他们使得代码更整洁,可测试,业务逻辑更清晰。
在添加一个便利构造器时,应该先确保是否是必须的。如果一个对象在init方法中只有几个参数,并且这些参数没有切确的默认值,那添加一个便利构造器是没必要的并且调用者应该直接使用标准的init方法。
为了配置我们的对象,我们将从4个点来收集我们的依赖:
1. 没有一个切确的默认值。 包括 boolean值或 number值,他们可能根据在不同实例变量中的值各不相同。所以这些值应该作为参数传递到便利构造器中;
2. 存在共享对象。 这个也需要作为参数传递到便利构造器中(比如一个无线电频率)。这些对象之前可能已经作为单例或通过父类指针被赋值;
3. 被新创建的对象。 如果一个对象没有把这个依赖分享给其他对象,那其他对象(同一个类)应该在遍历构造器内创建一个新的依赖对象。
4. 系统单例。 Cocoa内提供的单例是可以直接被访问的,比如文件管理者单例[NSFileManager defaultManager],这里很明确在你的应用程序中只有一个实例将会被使用。
下面是关于赛车的简单初便利构造器
系统单例
在Cocoa中有许多对象只有一个实例存在,如[UIApplication sharedApplication]
,[NSFileManager defaultManager]
,[NSUserDefaults standardUserDefaults]
,和[UIDevice currentDevice]
等。如果一个对象依赖于这些对象中的一个,那就应该被作为参数包含进来。即使在你的应用程序中只有这样一个实例。在你的测试中可能想要模拟实例或在测试前创建一个实例来避免测试依赖。
这里建议避免在你的代码中创建全局的单例,而是在一个对象中创建一个单一的实例,当它第一次被使用时,将它注入到其他依赖它的对象中去。
有些时候,一个类的 初始化方法/构造方法 不能被修改或不能被直接调用。在这种情况下,你需要使用 setter 注入。如下代码:
copy
类注册
“类注册” 工厂模式的使用意味着对象不能修改它们的初始化方法。见代码:
copy
NSArray
@H_502_30@*raceCarClasses=@[
Storyboards 提供了很方便的方法来构建我们的界面,但是在依赖注入中它也带来了问题。 特别是当在Storyboard中实例化一个初始化的视图控制器,它不允许你选择调用哪一个初始化方法。 类似的,当在storyboard 中定义一个 segue 是,目标控制器在实例化时也不能让你指定调用那个初始化方法。
解决方法是避免使用 storyboard。这看起来是一种极端的解决方案,但是我们发现在大型团队开发中, storyboard 带来了其他问题。另外,不适用storyboard并没有丢掉它的所有好处,除了storyboard提供的segues外,XIB也提供了和 storyboard 相同的好处,而且Xib 可以让你自定义初始化方法。
公有和私有
依赖注入鼓励你在你的共有接口上暴露出更多是对象。正如上面提到的,这有很多好处。但是当在构建框架时,它使你的共有API变得臃肿。使用依赖注入之前,公有对象 A 已经使用私有对象 B(对象 B 反过来使用私有对象C),但是对象B 和对象C 从来没有从框架中暴露。通过依赖注入,对象A 在它的公有初始化方法中有对象 B ,而对象 B 反过来使得对象 C 在它的初始化方法中公开。
copy
@interface
@H_502_30@ObjectA
依赖注入在Objective-C中很自然的存在,在Swift中也一样。合理的使用它能让你的代码可读性更强,可测试性更好,可维护性更高。