本篇和大家一起学习IoC和DI即控制反转和依赖注入。
当然听上去这词语非常的专业,真不知道是怎么组出来的,看上去难归看上去难,但稍微理解一下也就这么回事了。
首先我们要明白IoC/DI干嘛用的,不然别人问“你知道IoC/DI吗?”,“知道啊!”,“那干嘛用的?”,呵呵,就郁闷了,其实IoC/DI的作用就两个字——解耦,说一千道一万,还是离不开这两个字。所以,IoC/DI的作用就是解耦。
至于为什么要解耦,解耦在面向对象开发中的重要性在这里就不做细说了。
下面我们来认识一下它们的真面目吧!
要理解IoC/DI两个概念,就必须搞清楚如下问题:
- 参与者都有谁?
- 依赖:谁依赖于谁?为什么需要依赖?
- 注入:谁注入谁?到底注入了什么?
- 控制反转:谁控制谁?控制什么?为什么叫反转?有没有正向呢?正向又是什么?
- 依赖注入和控制反转是同一概念吗?
下面来简要回答一下上述问题,把这些问题搞明白了,也就明白了IoC/DI。
- 参与者都有谁:一般有三方参与,一个是某个对象,一个是IoC/DI容器,还有就是某个对象的外部资源。
注:某个对象指任意一个普通的对象,IoC/DI容器简单说就是用来实现IoC/DI功能的一个框架程序,比如现在有许多IoC/DI框架(Unity、Spring.NET、Autofac),对象的外部资源指对象需要的,但是从对象外部获取的,都统称资源,比如对象需要其他对象。
- 谁依赖于谁:当然是某个对象依赖于IoC/DI容器
- 为什么需要依赖:因为对象需要IoC/DI的容器来提供对象需要的外部资源。
- 谁注入谁:IoC/DI的容器注入某个对象
- 到底注入了什么:就是注入某个对象所需要的外部资源,说完整点就是IoC/DI的容器将某个对象所需要的外部资源注入了该对象。
- 谁控制谁:IoC/DI的容器来控制对象
- 控制什么:主要是控制对象实例的创建
- 为什么叫反转:反转是相对与正向而言的。
- 正向又是什么:通常情况下的应用程序,如果在A里使用C,一般我们会直接创建C的对象,也就是说,在A类中主动获取所需要的外部资源,这种情况被成为正向的,那什么是反向呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向地注入到A类中。
先看没有IoC/DI的时候,常规A类使用C的示意图
当有了IoC/DI的容器后,A类不再主动去创建C了,而是被动等待,等待IoC/DI的容器获取C的实例,然后反向注入到A类中。
依赖注入和控制反转是同一概念吗?
根据上面讲述,依赖注入和控制反转是对同一件事情的不同描述。依赖注入使用应用程序的角度去描述,我们可以把依赖注入描述的完整点:应用程序依赖容器创建并注入它所需要的外部资源。控制反转是从容器的角度去描述的,描述的完整点就是:容器控制应用程序,由容器反向的向应用程序注入其所需要的外部资源。
好了,通过上述学习,大家都应该对IoC/DI有了基本了解,下面我们再来正式认识下依赖注入和控制反转。
依赖注入中,依赖关系就像被注入的液体,我们可以在任何时候将依赖关系注入到模块中,而不只局限于编译是绑定。这种依赖关系是通过注入的方式完成的,就意味着我们可以随时更新,因为注入的液体与模块本身并无直接关联,举个不恰当的比喻,我们难道天生就要和针筒打交道吗?
实现依赖注入的前提是面向接口编程,辅助的技术可以使用反射技术。
MF.将依赖注入的形式分为三种:构造函数注入、设置方法注入和接口注入。
接口注入是通过定义接口约束的方式实现依赖注入,会给容器带来设计的限制。
而构造函数注入与设置方法(可以是属性)注入则是使用类的构造函数以及自定义的setter方法或属性进行依赖注入(关于这两点,可以参考第一篇《设计模式之UML类图的常见关系(一)》中的聚合和组合关系)。
在这里我们也把第一篇聚合和组合关系类图拿到了。
下面这个是使用Setter方法/属性实现依赖注入的。说白了就是定义一个Setter方法/属性,在该方法/属性内将对象关联起来。
下面这个是构造函数的依赖注入,就是将Setter方法/属性换成了构造函数,但是请仔细看看两幅图片的代码和注入,什么时候使用Setter方法/属性注入,什么时候使用构造函数注入,当时在开发过程中,通常是使用构造函数进行依赖注入的。
还有一个就是接口注入。
这是我认为最不够优雅的一种依赖注入方式。要实现接口注入,首先ServiceProvider要给出一个接口定义:
public interface InjectFinder { void injectFinder(MovieFinder finder); }
接下来,ServiceUser必须实现这个接口:
class MovieLister: InjectFinder { private MovieFinder finder; public void injectFinder(MovieFinder finder) { this.finder = finder; } }
为什么现在介绍IoC/DI呢?
想想,前面我们已经学习了三种工厂,那能不能将工厂和IoC/DI联系起来呢?好了,下面就自己想想!
小结:IoC/DI对编程带来的最大改变不是在代码上,而是在思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动了,被动的等待IoC/DI容器来创建并注入它所需要的资源。