揭秘依赖注入:软件开发人员的基本指南

Dependency injection (DI) is a design pattern and programming technique to manage dependencies between different components.
依赖注入(DI)是一种用于管理不同组件之间依赖关系的设计模式和编程技术。

In DI, the dependencies of a class or other dependent component are constructed and provided from outside (injected) rather that begin created by depended component.
在 DI 中,类或其他依赖组件的依赖关系是从外部构造和提供(注入)的,而不是由依赖组件开始创建的。

Understanding dependency injection is key to following the dependency inversion principle.
了解依赖注入是遵循依赖倒置原则的关键。

Core components 核心部件

The three main components of Dependency Injection are:
依赖注入的三个主要组成部分是:

  1. Dependency: A dependency is an object or service that another class relies on to perform its tasks. It represents a contract or interface that defines the required functionality.
    依赖关系:依赖关系是另一个类依赖于执行其任务的对象或服务。它代表定义所需功能的契约或接口。
  2. Client: The dependent class, also known as the client class, is the class that relies on dependency to fulfill its functionality. It typically declares a dependency through constructor parameters, setter methods, or interface contracts.
    客户端:依赖类,也称为客户端类,是依赖依赖来实现其功能的类。它通常通过构造函数参数、setter 方法或接口契约来声明依赖关系。
  3. Injector: The injector (aka container, assembler, factory) is responsible for creating and managing the dependencies and injecting them into the dependent class (client). The injector can be a framework or container provided by a DI library, or it can be a custom implementation.
    注入器:注入器(又名容器、汇编器、工厂)负责创建和管理依赖项并将它们注入到依赖类(客户端)中。注入器可以是 DI 库提供的框架或容器,也可以是自定义实现。

These three components work together to enable the benefits of DI, such as loose coupling, modularity, testability, and flexibility.
这三个组件协同工作以实现 DI 的优势,例如松散耦合、模块化、可测试性和灵活性。

The dependent class relies on the injector or container to provide the required dependencies, and the dependencies themselves are defined as contracts or interfaces, allowing for interchangeable implementations. This separation of concerns and inversion of control leads to more maintainable and scalable code.
依赖类依赖于注入器或容器来提供所需的依赖项,并且依赖项本身被定义为契约或接口,从而允许可互换的实现。这种关注点分离和控制反转导致代码更易于维护和扩展。

Implementation 执行

As an example, let’s consider two classes: Engine and Car.
作为示例,让我们考虑两个类:Engine 和 Car。

To construct an instance of the Car class, we need an appropriate Engine object.
为了构造 Car 类的实例,我们需要一个适当的 Engine 对象。

class Engine {private horsepower: number;private fuelType: string;constructor(horsepower: number, fuelType: string) {this.horsepower = horsepower;this.fuelType = fuelType;}public start() {console.log(`Engine started. Horsepower: ${this.horsepower}, Fuel Type: ${this.fuelType}`);}public stop() {console.log("Engine stopped.");}
}class Car {private engine: Engine;private brand: string;private model: string;constructor(brand: string, model: string) {this.brand = brand;this.model = model;this.engine = new Engine(200, "Gasoline");}public startCar() {console.log(`Starting ${this.brand} ${this.model}`);this.engine.start();}public stopCar() {console.log(`Stopping ${this.brand} ${this.model}`);this.engine.stop();}
}// Example usage
const car = new Car("Toyota", "Camry");car.startCar();
car.stopCar();// To consturct a car with Gasoline engine required a manual edit of Car class

For now, it works fine, except if we need to pass different parameters to the Engine class, it’s required a manual edit.
目前,它工作得很好,除非我们需要将不同的参数传递给 Engine 类,否则需要手动编辑。

Map of dependencies between the Car and Engine classes
Car 和 Engine 类之间的依赖关系图

Parameters injection 参数注入

To resolve such a problem we can take advantage of parameters injection. Let’s rewrite the current code.
为了解决这样的问题,我们可以利用参数注入。让我们重写当前的代码。

class Engine {// same implementation
}class Car {private engine: Engine;private brand: string;private model: string;constructor(brand: string, model: string, horsepower: number, fuelType: string) {this.brand = brand;this.model = model;this.engine = new Engine(horsepower, fuelType);}public startCar() {console.log(`Starting ${this.brand} ${this.model}`);this.engine.start();}public stopCar() {console.log(`Stopping ${this.brand} ${this.model}`);this.engine.stop();}
}// Example usage
const car1 = new Car("Toyota", "Camry", 200, "Gasoline");car1.startCar();
car1.stopCar();// Easy change Engine parameters
const car2 = new Car("BMW", "X5", 300, "Diesel");car2.startCar();
car2.stopCar();

Now the general logic does not change; instead, we can easily make changes according to our needs.
现在总体逻辑没有改变;相反,我们可以根据需要轻松进行更改。

Map of dependencies between the Car and Engine classes
Car 和 Engine 类之间的依赖关系图

Constructor/setter injection
构造函数/设置器注入

In the previous example, we used parameter injection to change horsepower and fuelType for Engine class. However, it may become cumbersome when dealing with a large number of dependencies.
在前面的示例中,我们使用参数注入来更改 Engine 类的 horsepower 和 fuelType 。然而,当处理大量依赖项时,它可能会变得很麻烦。

To make these 2 classes more flexible to change and testing, it is customary to create the necessary dependency outside the dependent class. You can attain this outcome by utilizing a constructor or setter injection.
为了使这两个类更灵活地更改和测试,通常在依赖类之外创建必要的依赖项。您可以通过使用构造函数或设置器注入来实现此结果。

class Engine {// same implementation
}class Car {private engine: Engine;private brand: string;private model: string;constructor(brand: string, model: string, engine: Engine) {this.brand = brand;this.model = model;this.engine = engine;}public startCar() {// same logic}public stopCar() {// same logic}
}// Example usage
const gasolineEngine = new Engine(200, "Gasoline");
const car1 = new Car("Toyota", "Camry", gasolineEngine);car1.startCar();
car1.stopCar();// Easy change Engine parameters
const dieselEngine = new Engine(300, "Diesel");
const car2 = new Car("BMW", "X5", dieselEngine);car2.startCar();
car2.stopCar();

By removing the responsibility of creating the engine instance from the Car class, you adhere to the Single Responsibility Principle. The Car class should focus on its own responsibilities related to the car's behavior, while the engine creation and configuration can be handled in a different part of the code.
通过从 Car 类中删除创建引擎实例的责任,您可以遵守单一责任原则。 Car 类应该专注于其自身与汽车行为相关的职责,而引擎的创建和配置可以在代码的不同部分中处理。

The same realization, but using setter injection:
相同的实现,但使用 setter 注入:

class Engine {// same implementation
}class Car {private brand: string;private model: string;private engine: Engine;constructor(brand: string, model: string) {this.brand = brand;this.model = model;}public setEngine(engine: Engine) {this.engine = engine;}public startCar() {// same logic}public stopCar() {// same logic}
}// Example usage
const gasolineEngine = new Engine(200, "Gasoline");
const car1 = new Car("Toyota", "Camry");
car1.setEngine(gasolineEngine);car1.startCar();
car1.stopCar();const dieselEngine = new Engine(300, "Diesel");
const car2 = new Car("BMW", "X5");
car2.setEngine(dieselEngine);car2.startCar();
car2.stopCar(); 

Map of dependencies between the Car and Engine classes
Car 和 Engine 类之间的依赖关系图

Interface injection 接口注入

Right now, the current implementation of Car is tied to a specific Engine class. This can be a problem if individual instances of the Engine class requires different logic.
现在, Car 的当前实现与特定的 Engine 类相关联。如果 Engine 类的各个实例需要不同的逻辑,这可能会出现问题。

To make the Engine and Car classes more loosely coupled, we can bind Car to an interface (or abstract class as an interface) instead of a specific child Engine class.
为了使 Engine 和 Car 类更加松散耦合,我们可以将 Car 绑定到接口(或作为接口的抽象类)而不是特定的子 < b3> 类。

interface Engine {start(): void;stop(): void;
}class GasolineEngine implements Engine {private horsepower: number;private fuelType: string;constructor(horsepower: number) {this.horsepower = horsepower;this.fuelType = "Gasoline";}public start() {console.log(`Gasoline engine started. Horsepower: ${this.horsepower}`);}public stop() {console.log("Gasoline engine stopped.");}
}class DieselEngine implements Engine {private horsepower: number;private fuelType: string;constructor(horsepower: number) {this.horsepower = horsepower;this.fuelType = "Diesel";}public start() {console.log(`Diesel engine started. Horsepower: ${this.horsepower}`);}public stop() {console.log("Diesel engine stopped.");}
}class Car {private engine: Engine;private brand: string;private model: string;// class Car expect any valid Engine implementationconstructor(brand: string, model: string, engine: Engine) {this.brand = brand;this.model = model;this.engine = engine;}public startCar() {// same logic}public stopCar() {// same logic}
}// Example usage
const gasolineEngine = new GasolineEngine(200);
const car1 = new Car("Toyota", "Camry", gasolineEngine);car1.startCar();
car1.stopCar();const dieselEngine = new DieselEngine(300);
const car2 = new Car("BMW", "X5", dieselEngine);car2.startCar();
car2.stopCar();

Now the Car class is decoupled from the specific implementation of the Engine class. This allows you to easily substitute different engine types without modifying the Car class itself.
现在, Car 类与 Engine 类的具体实现解耦了。这使您可以轻松替换不同的引擎类型,而无需修改 Car 类本身。

Map of dependencies between the Car and Engine classes
Car 和 Engine 类之间的依赖关系图

Injectors 喷油器

So far, I’ve been talking only about dependencies and clients.
到目前为止,我只讨论依赖项和客户端。

Manual creation of dependencies can be painful. Especially if there are multiple levels of nesting. That’s where injectors come in.
手动创建依赖项可能会很痛苦。特别是当有多层嵌套时。这就是注射器发挥作用的地方。

The injector resolves the dependencies and provides them to the client class. You can create your own algorithm for registering and injecting dependencies, or you can use DI containers or DI frameworks that will do this for you.
注入器解析依赖关系并将它们提供给客户端类。您可以创建自己的算法来注册和注入依赖项,也可以使用 DI 容器或 DI 框架来为您执行此操作。

Examples for JavaSript/TypeScript are InversifyJS, Awilix, TypeDI, and NestJS, for C# — ASP.NET Core Dependency Injection, Java — Spring Framework, and Go — Google Wire.
JavaSript/TypeScript 的示例包括 InversifyJS、Awilix、TypeDI 和 NestJS,C# — ASP.NET Core 依赖注入、Java — Spring Framework 和 Go — Google Wire。

Let’s rewrite the last implementation with an interface injection using the TypeDI container:
让我们使用 TypeDI 容器通过接口注入重写最后一个实现:

import { Service, Inject, Container } from 'typedi';
import 'reflect-metadata';interface Engine {start(): void;stop(): void;
}@Service()
class GasolineEngine implements Engine {private horsepower: number;private fuelType: string;constructor(@Inject('horsepower') horsepower: number) {this.horsepower = horsepower;this.fuelType = 'Gasoline';}start() {console.log(`Gasoline engine started. Horsepower: ${this.horsepower}`);}stop() {console.log('Gasoline engine stopped.');}
}@Service()
class DieselEngine implements Engine {private horsepower: number;private fuelType: string;constructor(@Inject('horsepower') horsepower: number) {this.horsepower = horsepower;this.fuelType = 'Diesel';}start() {console.log(`Diesel engine started. Horsepower: ${this.horsepower}`);}stop() {console.log('Diesel engine stopped.');}
}@Service()
class Car {private engine: Engine;private brand: string;private model: string;constructor(@Inject('brand') brand: string, @Inject('model') model: string, @Inject('engine') engine: Engine) {this.brand = brand;this.model = model;this.engine = engine;}public startCar() {console.log(`Starting ${this.brand} ${this.model}`);this.engine.start();}public stopCar() {console.log(`Stopping ${this.brand} ${this.model}`);this.engine.stop();}
}// Register dependencies with the container
Container.set('horsepower', 200);
Container.set('brand', 'Toyota');
Container.set('model', 'Camry');
Container.set({ id: 'engine', type: GasolineEngine }); 
Container.set({ id: Car, type: Car });Container.set('horsepower', 300);
Container.set('brand', 'BMW');
Container.set('model', 'X5');
Container.set({ id: 'engine', type: DieselEngine }); 
Container.set({ id: Car, type: Car });// Example usage
const car1 = Container.get(Car);
car1.startCar();
car1.stopCar();const car2 = Container.get(Car);
car2.startCar();
car2.stopCar();// console.log:
Starting Toyota Camry
Gasoline engine started. Horsepower: 200
Stopping Toyota Camry
Gasoline engine stopped.
Starting BMW X5
Diesel engine started. Horsepower: 300
Stopping BMW X5
Diesel engine stopped.

Using a DI container simplifies dependency and client management. This not only allows you to create a complex dependency graph but also makes it easy to test components with stubs and mocks.
使用 DI 容器可以简化依赖性和客户端管理。这不仅允许您创建复杂的依赖关系图,还可以轻松地使用存根和模拟来测试组件。

Conclusion 结论

In summary, Dependency injection is a valuable technique for designing flexible, modular, and testable software systems. It promotes loose coupling, enhances code reusability, and simplifies the configuration and management of dependencies.
总之,依赖注入是设计灵活、模块化和可测试的软件系统的一项有价值的技术。它促进松散耦合,增强代码可重用性,并简化依赖项的配置和管理。

By adopting DI, you can write more maintainable, scalable, and robust applications.
通过采用 DI,您可以编写更可维护、可扩展且健壮的应用程序。

References: 参考:

  1. Wikipedia: Dependency injection
    维基百科:依赖注入
  2. Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern
    Martin Fowler:控制容器反转和依赖注入模式

Thank you for reading this article! If you have any questions or suggestions, feel free to write a comment.
感谢您阅读这篇文章!如果您有任何疑问或建议,请随时发表评论。

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

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

相关文章

【Linux】-Linux基础命令[2]

目录 一、目录切换相关命令 1、cd 2、pwd 二、相对路径、绝对路径和特殊路径符 1、相对路径和绝对路径 2、特殊路径符 三、创建目录命令&#xff08;mkdir&#xff09; 四、文件操作命令 1、touch 创建文件 2、cat查看文件内容 3、more查看文件内容 4、cp命令复制文…

【Unix】FlatBuffers 在 C++ 项目中的使用教程

在 C 项目中使用 FlatBuffers 主要涉及以下几个步骤&#xff1a; 1. 安装 FlatBuffers 首先&#xff0c;你需要在你的系统上安装 FlatBuffers 编译器和库。你可以从 FlatBuffers 的 GitHub 仓库 下载源码并编译&#xff1a; git clone https://github.com/google/flatbuffer…

【JAVA |数组】数组定义与使用、常见的Arrays类介绍

目录 一、前言 二、数组的创建和初始化 三、数组的使用 四、数组是引用类型 1.JVM的内存分配 2.与引用类型变量 3.null 五、二维数组 六、Java中Arrays类的常用方法 1. Arrays.fill ->填充数组 2. Arrays.sort ->数组排序 3. Arrays.toString ->数组打印 …

美易官方:美国经济已现裂痕?美联储再不降息,崩溃即将到来!

美国经济&#xff0c;一度被誉为全球最强大的经济体&#xff0c;如今似乎已显露出脆弱的迹象。华尔街的分析师们纷纷发出警告&#xff0c;呼吁美联储尽快降息&#xff0c;否则一场经济崩溃可能即将来临。 自美联储大幅加息以来&#xff0c;经济学家们一直警告称&#xff0c;高利…

那个在买珠宝的年轻人

金价搭上过山车&#xff0c;今年以来价格一路飙涨。 珍珠身价同步飙升&#xff0c;晋级珠宝圈“新宠”。 文玩圈“减龄”&#xff0c;盘珠串不再只是“老头乐”。 月薪3000的年轻人&#xff0c;悄悄实现“宝石”自由。 黄金珠宝走俏&#xff0c;这届年轻人到底有着怎样的珠宝…

开抖音小店需要交多少保证金?全类目选择,一篇了解

哈喽~我是电商月月 做抖音小店前大家都会搜索“入驻抖音小店需要准备什么东西&#xff1f;”其中就包含了一项&#xff1a;类目保证金的缴纳 那到底要交多少钱&#xff1f;很多新手朋友还是不太了解 今天我就给大家解答这个问题&#xff0c;首先&#xff0c;我们要知道抖店的…

高并发-线程池

文章目录 高并发-线程池2024心得好的博客必会面试题为什么使用线程池线程池的实现原理线程池的拒绝策略阻塞队列已满&#xff0c;在提交任务会发生什么问题threadlocal原理和使用多线程配置 高并发-线程池2024 心得 面试都是围绕着三高展开&#xff0c;高并发&#xff0c;高可…

2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)

2024年虚拟现实、图像和信号处理国际学术会议&#xff08;ICVISP 2024&#xff09; 2024 International Conference on Virtual Reality, Image and Signal Processing&#xff08;ICVISP 2024&#xff09; 会议基本信息 会议时间&#xff1a;2024年8月2-4日 会议地点&#x…

2024年5月软考,别再傻傻啃书了!

备考2024年软考&#xff0c;不听课也不刷题&#xff0c;只是看教材的话&#xff0c;想要考试通过&#xff0c;几乎是不可能的&#xff0c;特别是基础比较薄弱的考生。 为什么只看教材通不过&#xff1f; 如果只是把教材从头到尾看一遍&#xff0c;毫无目的地看书&#xff0c;…

数据结构-线性表-应用题-2.2-5

从顺序表中删除其值在给定值s与t之间&#xff08;包括s和t&#xff0c;且s<t&#xff09;的所有元素&#xff0c;若s或t不合理或顺序表为空&#xff0c;则显示出错信息并退出运行。 从前往后扫描顺序表L&#xff0c;用k记录元素值在[s,t]的元素的个数&#xff0c;对于不在其…

FPGA学习笔记(1)——Vivado和HLS

1 Vivado设计 1.1 FPGA基本知识 Xilinx Atrix-7使用6输入LUT结构&#xff08;0-63&#xff09;CLB&#xff1a;可配置逻辑块Slice&#xff1a;每个CLB包含2个Slice(包含查找表LUT和8位寄存器REG)布线池&#xff1a;围绕在CLB周围&#xff0c;衔接FPGA的资源调度I/O块&#xf…

代码随想录35期Day32-Java

Day32题目 LeetCode122.买股票的最佳时机 核心思想&#xff1a;很简单&#xff0c;只要第二天比第一天贵&#xff0c;就第一天买入&#xff0c;第二天卖出 class Solution {public int maxProfit(int[] prices) {// 只要后一天比这一天价钱高就买&#xff0c;然后第二天卖出…

okcc呼叫中心为什么软电话已经注册,但是坐席监控却显示离线?

这种情况可能有几种可能的原因&#xff1a; 网络连接问题&#xff1a; 坐席监控系统可能无法正常与软电话建立连接&#xff0c;这可能是由于网络故障、防火墙设置或路由问题等引起的。 配置错误&#xff1a; 可能存在软电话配置或坐席监控系统配置方面的错误&#xff0c;导致无…

Qt宏和关键字

Q_GADGET 这个宏是Q_OBJECT宏的弱化版本吧&#xff0c;不过它可以继续 使用属性机制、反射等特性&#xff0c;但是不能使用信号与槽QT_INCLUDE_COMPAT 兼容版本QT_BEGIN_NAMESPACE 防止命名污染Q_GUI_EXPORT 为了实现跨平台以及插件Q_FLAG(RenderHint)&#xff0c;Q_DECLARE_FL…

test我说话撒机房环境

testhfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.ztesthfsjafjdsbzvbcxn.z

启动配置 BOOT

在STM32F10xxx里&#xff0c;可以通过BOOT[1:0]引脚选择三种不同启动模式。 STM32微控制器实现了一个特殊的机制&#xff0c;系统可以不仅仅从Flash存储器或系统存储器启动&#xff0c;还可以从内置SRAM启动。 根据选定的启动模式&#xff0c;主闪存存储器、系统存储器或SRAM可…

centos常用命令介绍

CentOS是一个基于Linux的开源操作系统&#xff0c;它提供了大量的命令和工具&#xff0c;用于管理和配置系统。以下是一些CentOS中常用的命令及其简要介绍&#xff1a; 查看系统信息&#xff1a; uname -a&#xff1a;查看内核/操作系统/CPU信息。 head -n 1 /etc/issue&…

基于 docker-compose 部署 LNMP 架构

目录 前言 1、任务要求 2、Nginx 2.1 建立工作目录并上传相关安装包 2.2 编写 Nginx Dockerfile 脚本 2.3 准备 nginx.conf 配置文件 3、Mysql 3.1 建立工作目录并上传相关安装包 3.2 编写 Mysql Dockerfile 脚本 3.3 编写 my.cnf 配置文件 4、PHP 4.1 建立工作目录…

系统图表:洞察数据的价值与魅力

在数字化、信息化迅猛发展的今天&#xff0c;数据已经成为企业决策、科学研究、社会管理等领域的核心资源。而如何高效、准确地理解和利用这些数据&#xff0c;成为摆在我们面前的重要课题。系统图表作为数据可视化的重要呈现工具&#xff0c;不仅能帮助我们洞察数据的内在规律…

【Python】字典题

题目&#xff1a;输入一段文本&#xff0c;统计每个字符的个数 in_inputinput(“输入&#xff1a;”) dic{} for char in in_input: if char in dic: dic[char]1 # 字典添加键值对的方法&#xff0c;给字典给键和值的方法 else: dic[char]1 print(dic) for key,value in dic.i…