依赖注入的优缺点与最佳实践

1 什么是依赖注入

依赖注入,全称Dependency Injection,简称DI。

在我们深入探讨之前,先来聊聊“依赖”和“注入”这两个比较术语的词。打个比方,你可以把“依赖”想象成电器设备的外接电线,而“注入”就像是把这根电线插到电源插座里,使用依赖注入就好比是你不需要知道电源插座在哪里,只需要知道有人会在你需要的时候为你插上插座,让电流连通,使设备工作。

在软件设计中,“依赖”指的是一个类需要调用的其他类或者服务,而“注入”则是指将这些所需的类或服务传递给需要它们的类的过程。使用“依赖注入”技术,我们可以在运行时将依赖关系传递给对象,而不是让对象自己去创建或查找它们需要的依赖。

如此,则软件中的组件可以在不同环境下重复使用,就像家里的电器可以随意在不同的插座上使用一样,不用担心它们的电源问题。“依赖注入”让我们的代码更加灵活,更容易维护和扩展。

2 为什么需要依赖注入

2.1 为什么需要控制反转

依赖注入其实是控制反转(IOC)的一种实现方式。

控制反转(Inversion of Control,IOC)是一种设计原则,它让我们能够将程序的控制权从程序本身转移到外部容器或框架上。需要控制反转的具体理由主要包括如下几点:

  • 实际只需要对象提供的服务,不需要关心对象从何而来。
  • 时空转变时,可能需要不同的对象来提供类似的服务,比如对于数据库操作服务,单元测试时需要模拟操作,而实际运行时需要真实操作。
  • 拥有对象的控制权时,需要不断的改造自己,麻烦且容易出问题。比如依赖项的构造函数变化、具体实现的更改等。
  • 将对象创建的控制权交出去,让外部场景来提供合适的服务对象。这可以让程序更容易组件化,更方便组合。

想象一下,如果你是一位导演,你肯定希望能够专注于电影拍摄部分,而不是每天都忙于处理拍摄场地的预订、设备的采购这些琐事。这就是控制反转的魅力所在——它允许我们专注于核心业务逻辑,而将创建对象、管理生命周期等琐事交给框架去处理。

2.2 控制反转的需求举例:发消息

我们的业务需要向用户发送消息。最开始,我们可能使用短信来实现这一功能。随着技术的发展,我们可能改用微信消息,未来甚至可能使用一种全新的通讯方式——比如X信。

如果我们的代码直接依赖于某种特定的消息发送方式,每当需要改变时,我们都需要修改代码,这无疑是非常繁琐和容易出错的。控制反转让我们可以轻松应对这种变化,因为我们只需更换提供服务的对象即可。

2.3 控制反转的其他实现:依赖查找(DL)

除了依赖注入,依赖查找(Dependency Lookup,DL)也是实现控制反转的一种方式。它的思路是在需要的时候,我们主动向一个管理容器询问或查找我们需要的依赖。这种方式比较传统,仍然依赖于容器的API,就像你需要知道每个插座在哪里,以及如何打开开关一样,这对于软件的解耦并不是最佳选择。

3 实现方式

3.1 使用接口

通过接口来抽象依赖是一个比较好的编程实践。在这种情况下,组件不会直接创建它们需要的依赖,而是通过引用接口来获得这些依赖的具体实现。这就像是制定一个规则,所有需要电力的设备都必须有一个标准的插头,这样它们就可以从任何一个标准插座获得电力。

注意使用接口不是必需的,你完全可以将一个不实现接口的类型实例注入到程序中。

3.2 基于set方法

在这种情况下,组件会提供一个公开的set方法,容器可以通过这个方法将依赖传递给组件。这就好比每个电器都有一个开关,当需要电力时,你只需打开开关即可。

3.3 基于构造函数

基于构造函数的注入是最直接的方式之一。当创建一个新的对象时,我们可以在构造函数中传递所有需要的依赖。这就像是买一个需要电池的设备时,店家直接给你装好电池,你拿回家就可以直接使用。

3.4 基于注解

在一些现代编程语言中,我们还可以使用注解(Annotations)来实现依赖注入。例如,在Java中,我们可以在构造函数、set方法、私有字段等元素上添加"@Autowired"注解,这样容器就会自动为我们注入依赖。

这就像是有一个自动插电系统,你只需要打开电器,它就会自动为你供电。

注意Spring中不再推荐使用"@Autowired"直接注解字段,因为这样写容易出Bug,还会让程序和依赖注入框架产生耦合。

4 开发语言支持

很多开发语言本身或者通过第三方框架提供了对依赖注入的支持,下边是一些介绍。

4.1 C++

在C++标准库中没有对依赖注入的支持,但是社区有Google Fruit、Boost.DI、Hypodermic这样的框架,它们提供了依赖注入的能力。使用这些框架,C++开发者可以更容易地管理对象的生命周期和依赖关系。

4.2 Java

Java可能是依赖注入最为流行的领域之一,Spring Framework就是一个典型的例子。它提供了一整套依赖注入的解决方案,极大地简化了Java应用的开发。

4.3 .NET

在.NET世界中,ASP.NET Core默认就支持依赖注入,另外还可以选择Ninject、Autofac、Spring.NET等框架。这些框架使得.NET开发者可以轻松地实现依赖注入,使他们的应用程序更加模块化和可测试。

4.4 PHP

PHP也不例外,Phalcon和Laravel等框架提供了依赖注入的功能,使得PHP开发更加现代化,易于管理和维护。

---

这里为了让大家更好的感受,我们举一个Spring Boot的例子:

首先声明一个日志接口:

public interface ILogger {void log(String message);
}

然后创建一个实现类,我们使用 @Component 注解来标记 ConsoleLogger 作为一个可注入的组件。

import org.springframework.stereotype.Component;@Component
public class ConsoleLogger implements ILogger {@Overridepublic void log(String message) {System.out.println("Log: " + message);}
}

然后创建一个 Application 类,它使用 @Service 注解,并通过构造函数注入 ILogger 依赖。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class Application {private final ILogger logger;@Autowiredpublic Application(ILogger logger) {this.logger = logger;}public void run() {logger.log("Application is running.");}
}

在 SpringApplicationMain 主类中注入 Application 类的实例并运行它。SpringApplicationMain 类实现了 CommandLineRunner 接口,当 Spring Boot 应用启动时,run 方法将被自动调用,然后会执行 application.run()。

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
public class SpringApplicationMain implements CommandLineRunner {private final Application application;public SpringApplicationMain(Application application) {this.application = application;}public static void main(String[] args) {SpringApplication.run(SpringApplicationMain.class, args);}@Overridepublic void run(String... args) {application.run();}
}

优缺点

5.1 优点

  • 代码解耦:通过依赖注入,我们可以降低类与类之间的耦合度。这就好比你的手机、笔记本电脑和台式机可以使用同一个充电器一样方便。这种解耦让每个类更加专注于自己的职责,而不是担心如何与其他类协作。
  • 测试便利性:当你需要对某个类进行单元测试时,你可以轻松地替换它依赖的组件,使用模拟(Mock)对象来代替真实的实现。
  • 可维护性和可扩展性:随着业务的发展,需求会发生变化。依赖注入使得我们可以不改变现有代码的情况下,扩展或替换组件。这就像是为了应对不同的电压,你可以更换不同的电源适配器,而不是去修改电器本身。

5.2 缺点

  • 学习曲线:对于初学者来说,依赖注入可能会带来一定的学习挑战。理解控制反转和依赖注入的概念,以及如何在项目中正确使用它们,可能需要一定的时间和实践。
  • 过度设计:在一些简单的应用场景中,使用依赖注入可能会使得项目过度设计,增加不必要的复杂性。这就像是用一个巨大的电源站来给一盏台灯供电,虽然可行,但显然不是最优解。
  • 运行时性能:虽然现代依赖注入框架的性能已经非常优秀,但在一些性能敏感的场景下,依赖注入可能会引入轻微的性能开销。这种开销可以比喻为插座和电器之间连接的电线长度,虽然影响微乎其微,但在某些情况下需要考虑。

6 最佳实践

明确你的依赖:在使用依赖注入时,首先要明确你的类需要哪些外部依赖。这就像是在你搭建电路之前,需要知道哪些设备需要电源。

选择合适的注入方式:根据你的具体情况选择最合适的依赖注入方式,无论是setter注入还是构造器注入。这就像是选择合适的充电器插头,确保它与你的设备兼容。

避免过多的依赖:一个类不应该有太多的依赖,这会使得类变得难以管理和维护。想象一下,如果一个电器有太多的插头,使用起来就会变得非常麻烦。

使用依赖注入容器:使用依赖注入容器可以帮助你管理类的依赖关系,这就像是使用一个多功能的电源条,可以同时为多个设备供电。

结语

依赖注入作为一种设计模式,它旨在减少代码之间的耦合性,使代码更易于维护和测试。依赖注入的核心思想是将对象的创建与它们的使用分离开来。通过这种方式,依赖注入可以使代码更加灵活,更容易扩展和重用。随着软件行业的不断发展,依赖注入也在不断进化,它已经从一个编程技巧变成了一种软件设计的基本原则。

关注萤火架构,加速技术提升!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/344079.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

专业课120+总分380+海南大学838信号与系统考研经验分享-电子信息,信息与通信,人工智能,生物医学

今年专业课120,总分380顺利被海大录取,总结一下这一年来的复习经验,希望对大家复习有借鉴。特别提醒这两年专业课海南大学838信号与系统难度比较大,还考察了IDTFT,DTFT等,对离散域的考察颇多,不…

简单二分图判定与最大匹配算法

前言 二分图,又称二部图,英文名叫 Bipartite graph。 通俗一点就是一个无向图如果能划成两个非空点集,且两部分内部没有边,则这是一张二分图。 如果从颜色的角度来说,就是把节点染成黑色/白色,并且使得没…

Python--函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。 函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户…

LeetCode刷题.14(不用算法解决1557. 可以到达所有点的最少点数目)

给你一个 有向无环图 , n 个节点编号为 0 到 n-1 ,以及一个边数组 edges ,其中 edges[i] [fromi, toi] 表示一条从点 fromi 到点 toi 的有向边。 找到最小的点集使得从这些点出发能到达图中所有点。题目保证解存在且唯一。 你可以以任意顺…

电脑桌面便签在哪里设置?Win10电脑桌面便签设置指南

很多上班族在使用Win10电脑办公时,需要随时记录工作事项。例如,需要参加一场紧急会议但时间不确定,或者在电话沟通时需要记下重要的信息,甚至可能是需要快速记录工作计划、想法或者临时安排的场景。在这些情况下,如果有…

opencv拉流出现missing picture in access unit with size 4错误解决

0、应用场景问题 我们使用opencv作为拉流客户端,获取画面后进行图像处理并推流(使用ffmpeg库)。 opencv解码同样使用ffmpeg库。 我们要求opencv能根据业务不断进行拉流操作,等效的逻辑代码如下: while(1) {printf(&…

【Spring 篇】基于注解的Spring事务控制详解

嗨,亲爱的读者朋友们!欢迎来到这篇关于基于注解的Spring事务控制的博客。如果你曾为事务处理而头痛,那么这里将为你揭开事务的神秘面纱。我们将一步步深入探讨Spring事务的世界,用简单易懂的语言、充满情感色彩的文字,…

paddleocr的基本使用

paddleocr是paddlepaddle专门做ocr的库,我们简单用一下 参考 PaddleOCR—图片文字识别提取—快速使用教程_paddleocr使用教程-CSDN博客 目录 1 安装 1.1 前言 1.2 安装paddleocr 1.3 安装paddlepaddle 1.4 安装cuda 1.5 安装cudnn 1.6 配置 zlibwap…

C/C++--ProtoBuf使用

一.什么是ProtoBuf 1.序列化和反序列化概念 序列化:把对象转变为字节序列的过程,称为系列化。 反序列化:把字节序列的内容恢复为对象的过程,称为反序列化。 2.什么情况下需要序列化和反序列化 存储数据:将内存中的对象…

Jmeter接口自动化02--JMeter的安装和使用

p02 高清B站视频链接 2.1 Windows环境 首先需要安装JDK,然后再部署JMeter。注意,JMeter对JDK的版本是有要求的,一般至少要JDK8,这也是目前开发过程中使用频繁的版本。 1. 安装JDK 从官网下载JDK:https://www.oracl…

Transformer详解【学习笔记】

文章目录 1、Transformer绪论2、Encoders和Decoder2.1 Encoders2.1.1 输入部分2.1.2 多头注意力机制2.1.3 残差2.1.4 LayNorm(Layer Normalization)2.1.5 前馈神经网路 2.2 Decoder2.2.1 多头注意力机制2.2.2 交互层 1、Transformer绪论 Transformer在做…

Hive数据定义(1)

hive数据定义是hive的基础知识,所包含的知识点有:数据仓库的创建、数据仓库的查询、数据仓库的修改、数据仓库的删除、表的创建、表的删除、表的修改、内部表、外部表、分区表、桶表、表的修改、视图。本篇文章先介绍:数据仓库的创建、数据仓…