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

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 implementation
  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 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.
感谢您阅读这篇文章!如果您有任何疑问或建议,请随时发表评论。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机台统一管理有多困难?但现在出现可行的解决方案了

机台的统一管理对于企业来说对提高生产效率、降低成本、优化资源配置以及保障生产安全等方面都具有至关重要的作用。但企业机台统一管理却存在实际的困难&#xff0c;主要体现在&#xff1a; 多样化的设备和运作模式&#xff1a;由于机台设备可能来自不同的厂商&#xff0c;其规…

如何描述自己的算法?

算法的表达 好的&#xff0c;让我们来一起探讨如何向别人介绍我们的算法。说话很简单&#xff0c;但要把话说清楚&#xff0c;说明白就不那么容易了。同样的道理&#xff0c;能够通俗易懂&#xff0c;直观清晰和严谨地描述一个算法&#xff0c;也是一项具有挑战性的任务。接下…

106短信群发平台在金融和法务行业的应用分析

一、金融行业应用 1.客户通知与提醒&#xff1a;银行、证券、保险等金融机构经常需要向客户发送各类通知和提醒&#xff0c;如账户余额变动、交易确认、扣费通知、理财产品到期提醒等。106短信群发平台可以快速、准确地将这些信息发送到客户的手机上&#xff0c;确保客户及时获…

.NET邮箱API发送邮件的步骤?怎么配置API?

.NET邮箱API发送邮件需要注意哪些&#xff1f;如何使用API发信&#xff1f; 在.NET环境中&#xff0c;使用邮箱API发送邮件是一个常见的需求。无论是企业级的邮件通知&#xff0c;还是个人项目中的邮件验证&#xff0c;都少不了.NET邮箱API的帮助。下面&#xff0c;AokSend将详…

我的创作纪念日(365天)

时间如白驹过隙&#xff0c;转眼间&#xff0c;我已经在技术写作这条路上走过了365个日夜。回望2023年5月9日&#xff0c;我敲下了第1篇技术博客的标题——《什么是代理IP&#xff1f;代理IP有什么用途》。那一天&#xff0c;平凡而又不平凡&#xff0c;因为我决定将自己的知识…

政安晨【零基础玩转各类开源AI项目】:基于Ubuntu系统本地部署使用GPT-SoVITS进行语音克隆与TTS语音生成

目录 介绍 什么是TTS 安装Miniconda 框架功能 测试通过的环境 开始 1. 安装好miniconda 2. 进入下载的GPT-SoVITS目录 3. 创建虚拟环境并执行脚本 4. 执行过程中可能会出错 5. 下载预训练模型 6. 训练过程中可能会报错 7. 使用过程中可能出错 8.以下是使用全过程…

信息安全管理体系介绍(含全套体系文档)

信息安全管理体系英文全称Information Security Management System&#xff0c;简称为ISMS&#xff0c;是1998年左右从英国发展起来的信息安全领域中的一个全新概念&#xff0c;是管理体系&#xff08;Management System&#xff0c;MS&#xff09;思想和方法在信息安全领域的应…

Flask-大体了解介绍

初识Flask Flask是使用 Python编写的Web微框架。Web框架可以让我们不用关心底层的请求响应处理&#xff0c;更方便高效地编写Web程序。 Flask主要有两个依赖&#xff0c;一个是WSGI&#xff08;Web Server Gateway Interface&#xff0c;Web服务器网关接口&#xff09;工具集…

探索大语言模型在信息提取中的应用与前景

随着人工智能技术的快速发展&#xff0c;大语言模型&#xff08;LLMs&#xff09;在自然语言处理&#xff08;NLP&#xff09;领域取得了显著的进展。特别是在信息提取&#xff08;IE&#xff09;任务中&#xff0c;LLMs展现出了前所未有的潜力和优势。信息提取是从非结构化文本…

ChatGPT-Next-Web漏洞利用分析(CVE-2023-49785)

1. 漏洞介绍 ​ 日常网上冲浪&#xff0c;突然粗看以为是有关Chat-GPT的CVE披露出来了&#xff0c;但是仔细一看原来是ChatGPT-Next-Web的漏洞。漏洞描述大致如下&#xff1a;&#xff08;如果有自己搭建了还没更新的速速修复升级防止被人利用&#xff0c;2.11.3已经出来了&am…

解决ModuleNotFoundError: No module named ‘skfuzzy‘,这个库全名可不叫skfuzzy哦,否则直接报错!!

ModuleNotFoundError: No module named skfuzzy 在这里插入图片描述在这里插入图片描述如何解决 ModuleNotFoundError: No module named skfuzzy 的问题&#xff1f;skfuzzy 模块介绍什么是模糊C均值聚类&#xff1f;skfuzzy 的应用如何使用 skfuzzy 进行模糊聚类 结论 如何解决…

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

1&#xff09;算法基本设计思想&#xff1a; 2&#xff09;c语言描述&#xff1a; #define INT_MAX 0X7FFFFFFF int abs_(int a) {//绝对值if(a<0) return -a;else return a; } bool min(int a,int b,int c){if(a<b&&a<c) return true;else return false; } …

JAVA随记——集合篇

注意&#xff1a;作者之前的Java基础没有打牢&#xff0c;有一些知识点没有记住&#xff0c;所以在学习中出现了许多零散的问题。现在特地写一篇笔记总结一下&#xff0c;所以有些知识点不是很齐全。 集合中各类名词的关系 Collection集合为单列集合。 集合存储数据类型的特点…

案例导入说明.md

案例导入说明 为了演示多级缓存&#xff0c;我们先导入一个商品管理的案例&#xff0c;其中包含商品的CRUD功能。我们将来会给查询商品添加多级缓存。 1.安装MySQL 后期做数据同步需要用到 MySQL 的主从功能&#xff0c;所以需要大家在虚拟机中&#xff0c;利用 Docker 来运行一…

即将开幕,邀您共赴创新之旅“2024上海国际消费者科技及创新展览会”

备受期待的2024上海国际消费者科技及创新展览会&#xff08;以下简称“CTIS”&#xff09;即将于6月13日至15日亮相上海新国际博览中心N1-N3馆。 2024上海国际消费者科技及创新展览会总面积达40,000平方米&#xff0c;涵盖600余家展商&#xff0c;预计吸引40,000多位观众莅临现…

js原生写一个小小轮播案例

先上示例&#xff1a; 附上代码 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content…

陪诊系统|陪诊小程序成品|陪诊系统功能

随着人们对健康的日益关注以及医疗技术的不断进步&#xff0c;陪诊小程序应运而生&#xff0c;通过提供陪同就医、医疗服务和健康管理等功能为患者和家庭成员提供了更多的便利和选择。本文将分析陪诊小程序的关键功能&#xff0c;以便更好地理解其在医疗领域的作用。 在陪诊小程…

SpringBoot过滤器简单构建详细教程以及与拦截器区别解释

作用范围&#xff1a;过滤器基于Servlet规范&#xff0c;作用于更广泛的层面&#xff0c;不仅限于Spring MVC&#xff0c;它可以拦截进入Web应用的所有请求&#xff0c;包括静态资源请求。过滤器可以对请求和响应的内容进行预处理和后处理。实现方式&#xff1a;过滤器需要实现…

iPhone 数据恢复软件 – 恢复丢失的 iPhone 数据

恢复丢失的 iPhone 数据&#xff0c;奇客数据恢复iPhone版。如今的 iPhone 用户在他们的设备上存储了大量数据&#xff0c;从照片和与亲人的文本对话到商业和医疗信息。其中一些是保密的&#xff1b;其中大部分内容都是非常个人化的&#xff1b;而且大多数一旦丢失就无法替代。…

4G水电燃气表定时拍照云端识别抄表仪器

通信方式&#xff1a;4G全网通 通信频段&#xff1a;B1/B3/B5/B8/B34/B38/B39/B40/B41 传输速率&#xff1a;最大10Mbps(DL)/最大5Mbps(UL) 传输功率&#xff1a;≤23dBm2dB 图片尺寸&#xff1a;640*480 JPG 图片大小&#xff1a;10~20K 光源条件&#xff1a;自带补光&a…