前言
乍听依赖注入,似乎是什么很高级的东西,其实不然,其只是名字高大上,我们实际上在不知不觉间就经常用它了。(本文只记录普遍性结论,不在某个框架下讨论)
什么是依赖注入
依赖注入是软件工程中的一个名词,不止存在于JAVA语言中,在多种语言中都有体现。本文只针对JAVA语言举例。
从软件工程的角度来看,依赖注入(Dependency Injection,简称DI)是一种设计模式,旨在减少软件组件之间的紧耦合,并促进代码的重用性和可测试性。在软件工程实践中,依赖注入提供了一种方法来管理对象之间的依赖关系,使得系统结构更加清晰和模块化。
以上叙述比较书面化,简单来说,依赖注入是一种组件解耦的思想。
引入
举一个简单的例子,一个小孩想让一只猫唱歌。先看一组比较“传统”的代码:
class Cat {
public void sing(){
System.out.println("我是一只猫,快乐的猫猫!!");
}
}
class Person {
private final Cat cat = new Cat();
public void catTosing(){
cat.sing();
}
}
public class Main {
public static void main(String[] args) {
Person persion = new Person ();
persion.catTosing();
}
}
这样的代码可能有些奇怪,但它也完成了我们的要求。对该段代码进行分析,我们发现,代码有个很大的问题,即小孩和猫绑定了,当我们新建一个小孩的实例的时候,在其内部就同时新建一个猫的实例,小孩和猫两个组件完全绑定在一起了。
但是小孩可以随便找一只猫来叫,猫猫的叫声有多种选择,比如喵喵、咪咪、呵呵。那么我们应该要把猫和小孩“解绑”,使得小孩可以随意找一只猫来叫。我们最通常的写法应该是这样的:
abstract class Cat {
public void sing();
}
class DragonLi extends Cat {
@Override
public void sing(){
System.out.println("嗷呜");
}
}
class Person {
private Cat cat ;
public Person(Cat cat){
this.cat = cat;
}
public void catTosing(){
cat.sing();
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new DragonLi();
Person persion = new Person (cat );
persion.catTosing();
}
}
以上代码事实上就完成了一次依赖注入,在以上代码中,Person组件和Cat组件是解耦的,Person 类不再需要关心 Cat 是如何实现的,只要继承了 Cat ,你可以传入任何的类进去。
提出背景
在传统的软件开发中,一个对象(类、模块等)可能需要依赖于其他对象才能完成其功能。如果这些依赖关系是在对象内部创建或硬编码的,那么这些对象就会变得难以独立测试和复用。此外,当需要更改依赖项的行为时(例如,小孩想换一只会叫哥哥的猫),整个系统可能都需要进行大规模的修改。
如何工作
-
构造函数注入(Constructor Injection):依赖项作为构造函数的参数传递给对象。这是最常用的一种方式,因为它确保了依赖项在对象创建时已经被正确设置。比如,我们举的例子。
class Person { private final Cat cat ; public Person(Cat cat){// this.cat = cat; } public void catTosing(){ cat.sing(); } }
-
属性注入(Property Injection):依赖项通过对象的属性或字段设置。这种方式较为灵活,但可能会导致对象状态的不一致性。举例,我们不在构造函数中给cat赋值了,而是单独写一个setCat方法。
class Person { private Cat cat ; public void setCat(Cat cat){// this.cat = cat; } public void catTosing(){ cat.sing(); } }
-
方法注入(Method Injection):依赖项通过方法调用时作为参数传递。这种形式不太常见,通常用于某些特定场景下的依赖,通常用于临时性的或特定上下文的依赖。举例,我们干脆不在Person中保留cat这个属性了,而是直接修改catTosing方法。
class Person { public void catTosing(Cat cat){// cat.sing(); } }
好处
依赖注入带来了一系列的好处,包括但不限于:
- 降低耦合度:对象之间不再直接依赖彼此的实现细节,而是依赖于抽象接口或契约。
- 提高可测试性:可以更容易地为测试提供模拟对象或存根,以隔离待测对象的外部影响。
- 增加灵活性:依赖项可以在运行时动态配置,而无需重新编译或修改代码。
应用
在现代的软件开发中,依赖注入经常与IoC(Inversion of Control)容器一起使用,这种容器负责管理应用程序中的对象及其依赖关系,自动执行依赖注入的过程。例如Java中的Spring框架,.NET中的Autofac等。这些工具提供了自动化依赖注入的能力,使得开发者可以专注于业务逻辑,而不是依赖关系的管理。
如果单纯对某框架而言,依赖注入可能有不同角度的定义,就比如Spring框架,那就不是这篇文章要讨论的了。