深入理解IoC/DI设计原则

IoC/DI概念

IoC(Inversion of Control)控制反转

在Java开发过程中IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。IoC是一种让服务消费者不直接依赖于服务提供者的组件设计方式,也是一种减少类与类之间依赖的设计原则。

DI(Dependency Injection)依赖注入

即组件之间的依赖关系由容器在运行期决定,形象的来说即由容器动态的将某种依赖关系注入到组件之中。

依赖注入的目标并非为软件系统带来更多的功能,而是为了提升组件重用的概率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可以指定目标需要的资源,从而完成自身的业务逻辑,而不用关心具体的资源来自何处,由谁实现。

IoC/DI理解

控制反转

  • 谁控制谁?
    1. 正向:指的是当前程序的方向。例如A对象要使用B对象,现在是在A里面直接创建B的实例,然后再调用。简而言之就是程序需要什么就由程序主动去获取需要的资源,这个方向称为正向
    2. IoC/DI容器控制应用程序
  • 控制什么?
    1. IoC/DI容器控制对象本身的创建、实例化
    2. IoC/DI容器控制对象之间的依赖关系
  • 为何叫反转(对应于正向)?
    因为现在应用程序不能主动去获取外部资源,而是被动等待IoC/DI容器给它注入所需资源,所以将这种行为称之为反转
  • 哪些方面反转了?
    1. 创建对象
    2. 程序获取资源的方式
  • 为何需要反转?
    1. 引入容器之后体系更为松散,管理更加有序
    2. 类之间真正实现了松散耦合,使得开发、测试、维护升级都变得容易

IoC/DI并没有帮助我们实现任何的业务功能,原本该由应用实现的功能还是由应用自身完成。

依赖

  • 什么是依赖?
    1. 名词:依赖关系
    2. 动词:依赖的动作(注入)
  • 谁依赖于谁?
    应用程序依赖于IoC/DI容器
  • 为什么需要依赖?
    因为反转了之后应用程序依赖的资源都在IoC/DI容器里面
  • 依赖什么东西?
    应用程序依赖于IoC/DI容器,依赖IoC/DI容器为它注入所需要的资源(比如:依赖关系)。

注入

  • 谁注入谁?
    IoC/DI容器注入应用程序
  • 注入什么东西?
    注入应用程序需要的外部资源,比如依赖关系
  • 为何要注入?
    因为程序要正常运行需要这些资源

依赖注入和控制反转是同一个概念吗?

不是同一个概念,但是对同一件事情不对角度的描述。所以其实描述的是同一件事情,只是以不同的角度在思考。控制反转是从IoC/DI容器的角度来思考,而依赖注入是从应用程序的角度在思考。

参与者都有哪些?

参与者主要是应用程序和Spring

IoC/DI是什么?

  • IoC:就是使用IoC/DI容器反过来控制应用程序所需要的外部资源,这样一种程序的开发思想。
  • DI:就是应用程序依赖IoC/DI容器来注入所需要的外部资源,这样一种程序的开发思想。
  • 能做什么?
    松散对象的耦合
  • 怎么做?
    1. 可以自己实现
    2. 使用已经实现好的IoC/DI容器,例如:Spring
  • 用在什么地方?
    1. 凡是程序里面需要使用到外部资源的情况,都可以考虑使用IoC/DI容器。
    2. 比如工厂类现在就可以不用再使用了
  • 外部资源
    对一个类来讲所谓外部资源,就是指在自己类的内部不能得到或实现的东西,比如:在类里面要读取一份配置文件,那么这份配置文件就是这个类的外部资源。

IoC/DI基本思想

Java基本阶段

在C类里面需要使用接口A,最原始的写法是:

1
2
3
4
A a = new A1();

//或者
A a = new A2();

然后通过 a 来调用接口中的方法。对于C来说是主动实例化对象,直接获取它所依赖的资源。

  • 这种方法好吗?有什么问题?
    更换实现需要重新编译源代码,很难更换实现,难于测试,耦合实例生产者和实例消费者。

Factory阶段

上述方法进化到使用工厂模式(Factory),在C类里面需要使用接口A,应用工厂模式的写法是:

1
A a = Factory.createA();

然后通过a来调用接口的方法。对于C来说是被动实例化对象,通过工厂间接获取依赖,但对工厂类来说是主动的。

  • 这种方法好吗?有什么问题?
    更换实现需要重新编译源代码,很难更换实现,难于测试。

Factory + XML + 反射阶段

继续进化到使用工厂模式加配置文件,在C类里面需要使用接口A,应用工厂模式的写法是:

1
A a = Factory.createA();

然后通过 a 来调用接口的方法,在工厂类里使用配置文件来决定最终实例化的具体类。对C类来说是被动创建对象,间接获取依赖,对工厂来说也是被动的。

  • 这种方法好吗?有什么问题?
    在这种情况下还会遇到很多的问题,比如:如何实例化带参数的类,如何在对调用的方法传递值等等。

IoC/DI阶段

继续进化就到IoC/DI的层次了,在这种设计里面出现了IoC/DI容器,容器在对IoC/DI涉及的元素进行整体控制,并提供更多更好的通用服务。此时在C类里面需要使用接口A,以Spring为例:

1
A a = BeanFactory.getBean("XX");

然后通过 a 来调用接口的方法。

此时由容器来创建和装配对象,并管理对象生命周期。对于应用程序而言就是被动实例化和被动接受依赖了。

基本思想

  1. 把程序之间的依赖关系去掉
  2. 把程序对象设置到IoC/DI容器的配置中作为Bean
  3. 由IoC/DI容器来管理Bean的创建、实例化
  4. 由IoC/DI容器来把Bean之间的关系注入到需要这些关系的对象里面。

简而言之就是对象之间的依赖全部去掉,然后由IoC/DI容器来管理对象和对象之间的依赖关系。

功能

实现了对象之间的松散耦合

IoC容器

简单的理解就是:实现IoC思想并提供对象创建、装配以及对象生命周期管理的软件。

IoC理解

  1. 应用程序无需主动new对象,而是描述对象应该如何被创建,IoC容器帮你创建,即被动实例化。
  2. 应用程序不需要主动装配对象之间的依赖关系,而是描述需要哪个服务,IoC容器会帮你装配(即负责将它们关联在一起),被动接受装配。
  3. 主动变被动,体现好莱坞法则:别打电话给我们,我们会打给你。
  4. 体现迪米特法则(最少知识原则):应用程序不知道依赖的具体实现,只知道要提供某类服务的对象(面向接口编程):并松散耦合,一个对象应当对其他对象有尽可能少的了解,不和陌生人(实现)说话。
  5. 是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。

思考

  • IoC/DI等同于工厂吗?
    不等同。IoC/DI思想源于工厂而高于工厂,它是工厂+容器
  • IoC/DI跟以前的方式有什么不一样?
    1. 不需要再使用工厂了
    2. 在实际使用各个框架时会有不同

领会:主从换位的思想

使用IoC/DI容器开发需要改变的思路

  • 应用程序不主动创建对象,但要描述创建它们的方式。
  • 在应用程序代码中不直接进行服务的装配,但要描述哪一个组件需要哪一项服务,由容器负责将这些装配在一起。
  • 也就是说:所有的组件都是被动的,组件初始化和装配都由容器负责,应用程序只是在获取相应的组件后实现应用的功能即可。

小结

IoC/DI是思想不是纯技术实现。IoC只是控制权的转移到框架,所以不能因为实现了IoC就叫IoC容器,而一般除了实现了IoC外,还具有DI功能的才叫IoC容器,因为容器除了要负责创建并装配组件关系,还需要管理组件生命周期。