控制反转(Inversion of Control),简称IoC,它不是一门技术,而是一种设计思想,一个重要的面向对象编程的法则。它能指导我们如何设计出松耦合、更优良的程序。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试。如下图所示:
图一
但有了IoC容器后,我们可以把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。如下图所示:
图二
依赖注入(Dependency Injection),简称DI ,它也不是一门技术,它是一种实现 IoC 的方式,是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。如图二所示。
什么是容器?
容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。
在这里,容器就是指实现自动管理对象依赖关系,避免手工管理存在的缺陷,管理对象生命周期的环境。
下面用伪代码来说明,容器、控制反转、依赖注入三者之间的关系。
我们知道电脑一般有接入外置键盘、鼠标、U 盘的能力。如下:
但是这个能力一般要依赖USB接口,如下:
如果不使用依赖注入,我们一般会采用直接在程序内部new一个对象的方式,如下:
在永不升级的时候,这样做是没有太大问题的,因为我们同样可以实现一次操作终生使用的效果。但现实是即使强大的神机也会面临岁月的折磨,我们的 USB 接口同样在某天会变得老化,速度颇慢,跟不上时代的步伐,更不兼容最新的 Type-C 接口。于是我们需要升级,但是,如果像之前那样直接new,那你需要直接修改核心逻辑,如果有多个地方使用,那你就需要修改多个地方。就等同于你要更换一个USB 接口,那你需要更换主板(忽略处理器、内存等接口版本差异),拆机、装机、插跳线、配置 BIOS 等工作,极其麻烦。那我们是否可以将这个工作交给 别人 来完成,从而我们仅仅当一个电脑的使用者就好?
将这个复杂的工作、控制权交给所谓的“别人”替我们完成的思想就叫做 控制反转。
而我们将这项工作移交给 帮手 来完成,交给帮手完成的操作实现就是 依赖注入。
依赖注入有两种方式,一种是通过构造函数,如下构造函数中的Helper参数:
另一种是通过setter方法进行赋值。
从上图我们可以看到,只要我们通过$helper注入不同的USB接口对象,这台Computer就可以实现不同的USB切换。
虽然,现在可以实现依赖注入,但是每次都需要我们手动传递。如果这样的依赖注入有很多,后期维护将是一个繁杂的工程。
这时容器的作用就体现出来了,这里容器就是根据类名,自动实现类的实例化,并且调用相关的方法,如下图所示。
这里的关键就是类的反射。通过反射获取类的构造函数及参数,然后通过构造函数实现依赖注入,最后达到控制反转的目的。