本文是我对于控制反转这个概念的理解,从接触IOC到很长的一段时间,我都以为自己理解了控制反转的概念,但实际回过头问自己究竟什么是控制反转,又很难清晰解释。通过不断反问自己问题,搜寻相关的文章,最后才找到了本文的答案。其中另外一个感触是,对于技术当中的概念术语,应该尽可能参考英文原文的解释才是最直接有效的理解方式,而很多中文解释其实大多参杂了那个人对于这个概念的理解,吃别人嘴里吐出来的东西营养价值就不多了。
IOC就是把对象的控制权交给容器?
IOC
作为Spring
的特性之一,经常会拿来和Spring
一起讨论。正如我第一次接触到IOC
的时候,也是在学习Spring
的时候。很长一段时间我对于IOC的理解,和搜索引擎的结果的都差不多。
比如,在你真的思考过IOC容器吗?这片文章中,作者对于IOC
的理解如下:
Ioc是把对象的控制权交给框架或容器,容器中存储了众多我们需要的对象,然后我们就无需再手动的在代码中创建对象。需要什么对象就直接告诉容器我们需要什么对象,容器会把对象根据一定的方式注入到我们的代码中。注入的过程被称为DI。
通过对于这段话的理解,IOC
做的事情就是无需再手动的在代码中创建对象。需要什么对象就直接告诉容器我们需要什么对象
。貌似听起来挺有道理,控制反转,就是将创建对象的控制权从对象本身反转到了Spring IOC
容器。可是,我解释不来我心中的另外一个疑问:难道这样做的目的仅仅就是帮助开发者减少一些创建对象的代码吗?
我对此表示否定,感觉以上文章更多介绍的是IOC
容器,而并非IOC
控制反转 概念本身。
脱离Spring:从Martin Flower的文章找答案
IOC
概念出自于Martin Flower
的一篇文章,Inversion of Control Containers and the Dependency Injection pattern,我找到了一篇ThoughtWorks
的译文,IoC容器和Dependency Injection模式。
在这篇文章中,作者用一个MovieFinder
的例子介绍了控制反转这个概念:
1 | class MovieLister... |
作者同时提出一个问题:
这个实现类的名字就说明:我将要从一个逗号分隔的文本文件中获得影片列表。你不必操心具体的实现细节,只要设想这样一个实现类就可以了。如果这个类只由我自己使用,一切都没问题。但是,如果我的朋友叹服于这个精彩的功能,也想使用我的程序,那又会怎么样呢?如果他们也把影片清单保存在一个逗号分隔的文本文件中,并且也把这个文件命名为” movie1.txt “,那么一切还是没问题。如果他们只是给这个文件改改名,我也可以从一个配置文件获得文件名,这也很容易。但是,如果他们用完全不同的方式——例如
SQL 数据库
、XML 文件
、web service
,或者另一种格式的文本文件——来存储影片清单呢?在这种情况下,我们需要用另一个类来获取数据。
1 | class MovieLister... |
Dependency Injection模式的基本思想是:用一个单独的对象(装配器)来获得MovieFinder的一个合适的实现,并将其实例赋给MovieLister类的一个字段。
在WIKI 中,我找到关于控制反转最贴切的解释:
Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。
采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
IOC的目的:依赖解耦
与其回答什么是控制反转,更好的出发点是,控制反转有什么好处,能带来什么?
答案就是:解耦
我们举这篇回答中的例子来说明。
假如我们需要在文本框对输入的文本进行拼写校验,如下代码可以满足我们的需求。
1 | public class TextEditor { |
但现实情况中,文本框中的文本并非一定是英文,有可能是中文,也有可能是其他语言或者混合语言。那么以上逻辑就需要扩展出新的checker
,可是当前的checker
是在TextEditor
中创建出来,我们需要换一个check
的话需要新写一个类。
到这里,checker
作为TextEditor
的依赖,关系就耦合在一起,一旦下层依赖需要发生变动,就一定会影响到上层的代码。暂且不论这样写好不好,类比于现实生活,如同在输出框中输出的不一定是英文,强行将checker
和TextEditor
绑定在一起就是不合适的(不是说不对,只能说不合适)。
那么最简单的方式当然就是,把创建依赖的逻辑放置到外层,通过注入的方式将依赖设置到属性当中。
1 | public class TextEditor { |
这样做的好处是,极大方便了checker
的扩展,也解除了TextEditor
和checker
的耦合关系。和主动创建相比,这时依赖必须先创建再注入到对象属性当中。这时,我们只要针对不同的语言环境,new
出不同的SpellChecker
,而不用改写TextEditor
里面的逻辑。
1 | SpellChecker sc = new SpellChecker; // dependency |
总结
那么我对于控制反转的理解是:控制反转,反转的是依赖的创建权
,由本来自己的依赖自己主动创建,反转成了依赖在外部创建,创建好了再注入进来的模式,其中,DI
(依赖注入)是实现控制反转的方式而已。
所以,这里解答了我对于之前关于IOC理解的一个误区,我以前一直以为IOC
就如文章前头说,反转的实例创建的创建权,讲创建权交给了容器而已,并没有理解到更本质的含义。IOC
其实更多可以解释为一种模式或者写法,这种写法带来的好处就是解除依赖的耦合。而Spring IOC
只是套用了这种模式,简化了依赖创建的部分,将对象和对象的依赖统一管理起来,更方便开发者使用。