控制反转( IOC )
控制反转的英文全称是Inversion ofControl (简称I0C),是近年来兴起的一种Java编程模式。该编程思想主要用于协调组件之间相互依赖关系,是面向对象思想的一种具体表现形式。
什么是控制反转
有一句好莱坞名言可以准确地概括出控制反转的本质:“Don't call us, we'll call you”(你呆着别动,到时我会找你)。
控制:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建
反转:传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象。为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转。
先简单回顾面向对象的思想。面向对象=类+对象+继承+消息,面向对象将组件分类,使用时再实例化,类与类之间可以继承,它们通过消息互相联系。另一个要提到的概念是服务,在很多面向对象文章中都涉及到了这个概念,那么什么是服务呢?服务就是一个类向另一个类提供该类所需要的接口(或方法)。下面通过一个简单的例子展示面向对象的思想,具体代码如下:
class Computer {
CPU cpu = new CPU();
public void computerwork() {
cpu.cpuwork();
}
}
class CPU {
public void cpuwork() {
System.out.println("computer is working");
}
}
上段代码中定义了一一个Computer类和一个CPU类,Computer类要引用CPU类。在这里另外定义两个概念:服务提供者与服务使用者。从上面的例子可以知道CPU类是服务的提供者,而对应的Computer类就是服务使用者。在它们之间采用的联系方式称为消息。
从上面的例子可以看出,服务提供者提供服务给服务使用者是采用直接调用的方式。这样带来如下弊端:如果服务提供者接口发生了变化,这样是不是需要改动每个服务使用者?这样的种依赖关系如何来解决呢?答案就是控制反转,它的引入让服务使用者不直接依赖于服务提供者。
从上面的例子可以看出,传统的软件架构中,由程序内部代码来控制组件之间的关系。我们经常使用new关键字来实现两组件间关系的组合,这种实现的方式会造成组件之间互相耦合。控制反转能很好地解决该问题,它将实现组件间的关系从程序内部提到外部容器来管理。也就是说,由容器在运行期将组件间的某种依赖关系动态地注入组件中。控制程序间关系的实现交给了外部的容器来完成。
注意: 一个好的设计,不但要实现代码重用,还要将组件间关系解耦。
控制反转是一种让服务使用者不直接依赖于服 务提供者的一种组件设计方式,一种减少类与类之间依赖和耦合的设计原则,一种使服务与服务提供者完全分开的设计原则。下面对于同一个代码架构例子采用IOC的思想再实现一次, 具体代码如下所示::
class Computer {
CPU cpu;
public void setCpu(CPU cpu) {
this.cpu=cpu;
}
public void computerwork() {
cpu.cpuwork();
}
}
class CPU {
public void cpuwork() {
System.out.println("computer is working");
}
}
public class client {
CPU cpu = new CPU();
public void execute() {
Computer com = new Computer();
com.setCpu(cpu);
com.computerwork();
}
}
观察以上代码可以发现,整个CPU类的控制权全部在客户端程序,即CPU给提供的服务方式最后提交给了客户端,完全取决于客户端的需要。不再是Computer 调用服务时就依赖于CPU。服务提供者(CPU)与使用者(Computer)之间的依赖就少了很多。 这就是IOC的一种具体实现方式。可以看出,I0C 更通俗的一种解释就是“ 在需要服务时才告诉你怎么去提供服务”。CPU的定义在Computer中只是一个预留的定义,直到client最终使用时才将具体怎么使用CPU通知给Computer。
了解了控制反转的含义后,再回到开始总结的那句话:“Don't call us, we'll call you”觉得这句话对IOC的形容非常贴切、生动。
控制反转的设计方式
最常用的控制反转的设计方式主要有3种。
(1)基于接口的设计方式
(2)基于JavaBeans的setter方法的设计方式,如:Spring框架。
(3)基于构造函数的设计方式,即通过构造函数传递服务提供者作为参数实现。
总结:可以发现使用IOC带来的代价是:需要在客户端或其他某处进行Computer和CPU之间联系的组装。所以,IOC并不是消除了Computer和CPU之间固有的联系,而只是转移了这种联系。
IOC和DI(Dependency Injection)
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
关系:依赖注入不能单独存在,需要在IOC基础上完成操作