依赖注入基础
依赖与依赖注入
首先,什么是依赖?
依赖是两个类之间的一种关系,其中一个类使用了另一个类的功能,也就是说,一个类依赖于另一个类。
在 OOP 中,依赖通常以在类中创建实例的形式出现。通过这个实例,一个类可以访问另一个类的方法。这样,类之间就产生了依赖。
例如,下面的伪代码展示了一个依赖关系。在 EmailService()
类中,我们创建了 Email()
的实例:
class Email ismethod sendMessage() is...class EmailService isEmail gmail = new Email()method sendMessage() isgmail.sendMessage()
这里,EmailService
类直接依赖于 Email
类。
什么是依赖注入?
依赖注入(Dependency Injection, DI) 就是把对象创建的任务转移到代码的其他部分。也就是说,把“依赖”作为外部传入的对象来使用。这样做的好处是可以让代码实现解耦合。
👉 关于松耦合的好处,可以参考 耦合与内聚(Coupling and Cohesion)。
目前并没有一致的观点认为 DI 是不是一种“设计模式”。这里作为入门介绍,解释会比较简化和主观。要更深入理解,你需要进一步学习 控制反转(IoC) 和相关的设计模式。
DI 示例
在第一个代码片段里,EmailService
依赖于 Email
类。因为它在类内部直接创建了 Email
实例,所以你无法切换不同类型的 Email
实现,也无法方便地测试。
为了解决这个问题,我们来看看使用 DI 改写的方式:
interface Email ismethod sendMessage()class GmailService implements Email ismethod sendMessage() is ......class EmailService ismethod sendMessage(Email service) isservice.sendMessage()
这里的变化是:
-
EmailService
依赖的是 接口 而不是具体类。 -
Email
接口可以有多个不同的实现,比如GmailService
。 -
对象的创建被移到了其他地方。
这样,我们就可以灵活地选择不同的实现:
EmailService gmail = new EmailService()
gmail.sendMessage(new GmailService())
依赖注入的三种方式
依赖注入主要有三种常见方式:
1. 方法(接口)注入
依赖通过方法传递进来。上面的例子就是方法注入。
2. 属性(Setter)注入
依赖通过单独的 setter 方法 注入。不同语言的实现方式可能不同,这里给出最简单的伪代码:
class EmailService isEmail servicemethod getService() isreturn servicemethod setService(Email service) isthis.service = serviceclass Main isEmailService email = new EmailService()email.setService(new GmailService())Email gmail = email.getService()gmail.sendMessage()
在 Java 中,配合 Spring 注解,你可以从配置文件里把参数注入到 set 方法里。
3. 构造函数注入
依赖通过类的构造函数传入:
class EmailService isEmail emailconstructor of EmailService(Email email) isthis.email = emailmethod sendMessage() isemail.sendMessage()class Main isEmailService gmail = new EmailService(new GmailService())gmail.sendMessage()
为什么需要依赖注入?
依赖注入的核心目的就是 降低耦合度。
通过 DI:
-
我们可以把对象创建的工作交给外部,不需要在类内部硬编码实例化过程。
-
一个类对另一个类的创建过程一无所知。
-
可以在不修改类本身的情况下重用它。
这对 代码扩展、修改、测试 都非常有帮助。
尤其是测试时,你可以传入不同实现或不同参数,模拟各种情况。
不过,DI 也有一些缺点:
-
学习曲线陡峭。
-
使用不当可能导致过度复杂化。
-
在不同框架和库中的实现方式可能各有不同,带来额外难度。
总结
-
DI 的核心目标:降低耦合度。
-
优点:提高代码灵活性、可扩展性,简化测试。
-
缺点:实现较复杂,存在学习成本。
-
实现方式:方法注入、Setter 注入、构造函数注入。
-
不同语言/框架的差异:实际实现会有区别。
虽然依赖注入一开始看起来比较复杂,但你现在已经了解了它的基本原理和作用。要更深入,可以继续学习 IoC 和相关的设计模式。