IOC 和 DI 先认为是一回事儿,以理解其中的思想为主。
让调用类对某一接口实现类的依赖关系由第三方(容器或者协作类)注入,以移除调用类对某一接口实现类的依赖。
IOC 的目的是为了解耦,具体是怎么解耦的呢?
IOC 具体实现可以总结为两点:
- 类的成员变量用接口类型(父类)声明
- 类实例交给第三方管理
1. 类的成员变量用接口类型(父类)声明
《Effective Java》 第 5 条:优先考虑依赖注入来引用资源
1 | public class SpellChecker { |
直接看上面的代码,可能会想,平时写的代码不就是这个样子吗?这有啥,跟依赖注入有啥关系?
这就是依赖注入。很多人已经使用多年,只是不知道它的名字而已。
没有对比就没有伤害,看下下面的代码,就能理解了
1 | public class SpellChecker { |
现在没有人会写这种代码了,大家已经习惯了上面的。
对比下面的代码,依赖注入的例子里,SpellChecker 只需要认识 Lexicon 这个接口,而不需要认识像 EnglishLexicon 或者 ChineseLexicon 这些具体实现类,就实现了 SpellChecker 与具体实现类的解耦,可复用性也更强,当换实现的时候,只需要在构造方法里,填充不同的实现类的实例即可。
1 | public class Main { |
2. 类实例交给第三方管理
在上面说注入实例的时候,用了这么一段代码。
1 | public class Main { |
这里其实就隐含了一个第三方(协作类): main 函数,它管理了 new EnglishLexicon() 和 new ChineseLexicon() 这两个实例,这种方式被称为 手动依赖注入。
关于网上说的纠结 这还不是自己 new 的么? 这个问题,现在应该有一个答案了吧,这是 手动依赖注入,其中的 手动 就是在自己 new。
别急,我知道你想说 Spring 这种框架,也可能还想说 工厂模式 创建 bean 之类的东西。
有 手动 那就得有 自动 对吧,Spring, Guice 这类 IOC 容器就是 自动依赖注入 要不 Autowired 咋个会有 Auto 呢?
new 这个动作,是可以自己一个一个去 new,远古时期的猿可能也就是这么干的,但慢慢聪明的猿发现,new 这个动作,实在只是个机械重复性动作,枯燥无聊,还业务无关,而计算机最擅长做的事情就是 重复,所以就把这些工作交给了框架来完成了。
那框架里又是怎么完成呢?无非就是 反射 + 配置文件,来实现 new 这个动作,当然说起来简单,做起来还是有各种困难的,什么循环依赖,引用未初始化完 bean 之类的问题也就接踵而至,具体实现细节,暂且不表,这里只是说明,自动 是怎么来的。
依赖注入框架 就是 自动依赖注入 的第三方了,总管实例的生成,存储,注入等等。容器本体是一个或者多个 Map {beanName: bean 实例}。
1 |
|
上面的代码就是现代使用 Spring 的时候的代码了,熟悉吧,当需要更换字典的时候就 return new ChineseLexicon();
使用配置文件的方式更能体现自动依赖注入的强大,也更有利于理解。
1 | <bean id="lexicon" class="com.demo.lexicon.EnglishLexicon"></bean> |
当需要修改实现的时候,只需要新建一个类,把 id 指过去就可以了,使用者甚至无需重新编译代码,只需要编译新增的这个类就可以了。
1 | <bean id="lexicon" class="com.demo.lexicon.ChineseLexicon"></bean> |
如果都是本来就有的类,根据业务调整,更换实现类,只需要改了配置,重启应用就可以了。
对于不知道 Spring 通过配置文件是怎么实例化 bean 的,一个简单示例
1 | // 从 xml 中读到 "lexicon" 和 "com.demo.lexicon.ChineseLexicon" 两个字符串 |