依赖注入(DI)和控制反转(IOC)
在软件工程领域,理解依赖注入(DI)与控制反转(IOC)不仅是提高编程效率的关键,也是实现高质量代码设计的基石。
理论基础
首先,控制反转(IOC)是一种设计原则,用于减少计算机代码之间的耦合。在传统的程序设计中,高级模块依赖于低级模块的实现细节,这种直接的依赖关系使得代码难以维护和扩展。控制反转的核心思想是通过抽象化来降低模块间的直接依赖,从而实现模块间的松耦合。在控制反转的模式下,不是由高级模块主动创建或查找所需的低级模块,而是将这一职责交给外部的容器或框架,由它来负责创建并维护对象之间的关系。
依赖注入(DI)是实现控制反转的一种技术手段。通过依赖注入,对象的依赖关系不再在对象内部静态地创建,而是在对象被创建的时候,由外部动态地注入。这种方式有三种基本形式:构造函数注入、属性注入和方法注入。构造函数注入是指通过构造函数传递依赖对象,属性注入则是通过公开的属性来接收依赖对象,而方法注入是通过调用方法的方式来传递依赖对象。无论采用哪种形式,依赖注入的目的都是将对象的创建和对象间的依赖关系的管理从业务逻辑中解耦出来,交给外部容器处理。
说明
例如:现有类 A 依赖于类 B
- 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
- 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)。
为什么叫控制反转?
控制:指的是对象创建(实例化、管理)的权力。
反转:控制权交给外部环境(Spring 框架、IoC 容器)。
IoC解决了什么问题?
IoC的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。
这样有什么好处呢?
对象之间的耦合度或者说依赖程度降低;资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。
很完美,这种方式也是可以实现的,但是我们想象一下如下场景:开发过程中突然接到一个新的需求,针对对IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new 的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao 的实现方式,那修改起来将会非常的头疼。
使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了。
结论
依赖注入和控制反转的好处是显而易见的。首先,它们能够显著提高代码的可测试性。由于依赖关系是外部注入的,因此在单元测试时可以很容易地用模拟对象(Mock Object)替换真实的依赖,这样就可以在隔离环境中测试代码的功能。其次,它们促进了代码的重用。由于依赖关系不再硬编码在组件内部,因此同一个组件可以在不同的环境中被重用,只需改变依赖对象的配置即可。最后,它们提高了代码的可维护性。当系统需要升级或替换组件时,由于组件之间的耦合度降低,改动的影响范围也随之减小。
在SpringBoot中的应用
在当今的软件开发实践中,Spring Boot作为一种轻量级的开源框架,已经广泛被用于创建高效、易维护的Java应用程序。Spring Boot的核心特性之一就是依赖注入(DI)和控制反转(IOC),这两个概念是Spring框架的基石,对于理解和有效使用Spring Boot至关重要。
控制反转(IOC)
在没有IOC的情况下,对象通常自行创建和管理它们所依赖的对象。这种方式导致了高度耦合和难以测试的代码。IOC的核心思想是将对象的创建和管理工作交给外部容器(在Spring框架中,这个容器通常是ApplicationContext),从而实现解耦。
依赖注入(DI)
通过DI,对象的依赖不再由对象本身在内部创建,而是在创建对象的时候由外部注入。这样做的好处是减少了组件之间的耦合,增加了代码的模块化,使得单元测试更加容易。
DI的实现方式
在Spring Boot中,DI可以通过以下几种方式实现:
- 构造器注入:通过对象的构造器来注入依赖。
- Setter注入:通过对象的Setter方法来注入依赖。
- 字段注入:直接在字段上使用@Autowired注解来注入依赖。
代码示例
为了更好地理解这些概念,让我们通过一些简单的代码示例来看看它们是如何在实践中应用的。
构造器注入
1 |
|
在上述代码中,UserService
依赖于UserRepository
。通过将UserRepository
作为构造器的参数,Spring容器在创建UserService
实例时自动注入UserRepository
的实例。
Setter注入
1 |
|
与构造器注入类似,但是这里依赖是通过Setter方法注入的。Spring容器会在创建UserService
实例后调用setUserRepository
方法来注入UserRepository
的实例。
字段注入
1 |
|
字段注入是最简单的注入方式,只需在字段上添加@Autowired
注解即可。Spring容器会自动寻找匹配的类型并注入相应的实例。
结论
通过上述讨论和代码示例,我们可以看到,依赖注入和控制反转在Spring Boot中是如何帮助我们实现低耦合、高内聚的设计的。通过将对象的创建和管理责任交给Spring容器,我们的应用程序变得更加模块化,易于测试和维护。
总结
IoC 和 DI 别再傻傻分不清楚。IoC是一种设计思想,或者说是某种模式,这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。并且,Martin Fowler 在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:https://martinfowler.com/articles/injection.html。
Martin 的大概意思是 IoC 太普遍并且不表意,很多人会因此而迷惑,所以,使用 DI 来精确指名这个模式比较好。