什么是控制反转
控制反转(Inversion of Control, 缩写IoC),是一种面向对象的设计原则。从字面上理解,就是让控制权从类本身移交给其他类。那么是哪些控制权被反转了?答案是:“依赖对象的创建”这个控制权被移交了。移交给谁了?答案是:IoC容器。
这样做带来的好处就是降低代码之间的耦合度,业务代码的对于底层类的依赖被降低后,底层实现就可以更容易地改变,业务代码也更容易被复用。
很多人都说控制反转可以用好莱坞规则来描述 “Don’t call us, we will call you.”。我们自己不需要自己创建依赖对象,IoC容器会帮我创建。
什么是依赖注入
依赖注入(Dependency Injection,缩写DI),是指动态地向某个类中注入(或者说创建)这个类所需要的依赖。很明显,这个概念和IoC密不可分,有人说DI是IoC的一种实现方式,也有人说DI就是IoC。过分纠结这些是没有必要的,如果你能有自己的理解,那么就会明白这两者究竟是什么关系。其实我们需要一个例子来加深理解。
Robot的依赖
1 | public class Robot{ // 我们有一个机器人类,它需要许多组件 |
Robot
依赖于两个接口RobotArm
和RobotFoot
的实现类。现在的Robot显然没有办法工作,因为依赖的对象没有被创建。于是,我们可以将2、3行的代码替换为
1 | private RobotArm arm = new GearArm(); |
这样,在Robot
的对象被创建时,它需要的依赖也会自动的被创建好。这种“依赖注入”,是Java在初始化对象时会帮我们做的。还有另一种方式是在构造函数中对arm
和foot
进行初始化,我们经常做这样的事情。达到的效果和上面是一样的。
Robot的耦合
上面这样做的优点就是简洁明了。缺点就是将Robot
与GearArm
WheelFoot
紧密关联在了一起,如果我们想给Robot换一个Arm,就需要再写一个RobotXXXArm的类,或者使用反射技术。所以耦合带来的影响就是不灵活,难以复用,难以改变底层实现。
使用依赖注入
看呐,要解决这些问题,就要使用依赖注入了。我们想使用什么样的Arm,IoC容器就给我们的Robot注入什么样的Arm对象。我们有四种方式可以让IoC容器给我们的Robot注入我们需要的依赖对象(XXArm和XXFoot)。
接口注入
被注入对象如果想要IoC容器为其注入依赖对象,就要实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。
1 | public class Robot implements RobotArmCallable{ |
从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退役状态”。因为它强制被注入对象实现不必要的借口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。
构造方法注入
在被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让IoC容器知道它需要哪些依赖对象。这就叫构造方法注入。
1 | public class Robot{ |
在学习面向对象编程之初,我们就已经会使用这种方法来注入依赖了,构造方法的用途便是这样。只不过现在我们可以更进一步,向构造方法传入参数的工作也可以交给IoC容器。
这种注入方式的优点就是,对象在构造完成之后,就会进入就绪状态,可以马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射技术构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
setter方法注入
对于JavaBean对象,我们通常会通过setXXX()和getXXX()方法来访问对应其private XXX属性。这些setXXX()方法统称为setter方法,getXXX()当然就称为getter方法。
1 | public class Robot{ |
因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值。缺点当然就是对象无法在构造完成后无法立即使用,无法立即进入就绪状态。
基于注解注入
1 | import org.springframework.beans.factory.annotation.Autowired; |
基于Java5.0之后引入的注解(Annotation),在私有变量前加@Autowired
注解(Spring Framework为我们提供的),不需要显式地加入额外代码,便可以让IoC容器注入对应的依赖对象。该方案相当于定义了setter方法,但是因为没有真正的setter方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为用作注入的setter只想让IoC容器访问来注入而并不希望其他人来修改此对象的私有域)。现在Spring也最推荐我们使用注解代替XML配置,所以基于注解注入的方式被使用的越来越多(因为确实很好用)。
尾巴
Robot类被这么一折腾之后,就使得类之间的耦合松散了,这里不仅是RobotArm
和RobotFoot
接口起到了解耦的作用,因为之前高耦合的代码一样使用了接口啊(其实接口并没有发挥效果,它们被错误地使用了),DI很好的诠释了接口的作用。使用了IoC之后,我们得到的额外好处就是便于我们写单元测试,测试驱动开发是一种很受重视的开发方式,没有IoC带来的松耦合,连测试都会变得困难。
用书上的一句话来概括 “IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。”