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

news2024/11/25 14:30:08

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

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

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

相关文章

厚德提问大佬答3:让AI绘画更有效率

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

深入分析网络智能摄像头的RTSP协议安全风险

本文为转载&#xff0c;原作者&#xff1a;山石网科安全技术研究院 网络摄像头作为现代安防体系的关键组成部分&#xff0c;已经广泛应用于各类场所&#xff0c;包括交通枢纽、教育机构、企业办公区、零售商场等公共和私人领域。它们主要负责提供实时视频监控&#xff0c;以加…

责任链模式和观察者模式

1、责任链模式 1.1 概述 在现实生活中&#xff0c;常常会出现这样的事例&#xff1a;一个请求有多个对象可以处理&#xff0c;但每个对象的处理条件或权限不同。例如&#xff0c;公司员工请假&#xff0c;可批假的领导有部门负责人、副总经理、总经理等&#xff0c;但每个领导…

【微积分听课笔记】全微分,二元极值,Double Integral

6.6 二元函数的极值_哔哩哔哩_bilibili 此笔记为听课笔记&#xff0c;宋浩老师微积分~ 最近诸事缠身&#xff0c;会有种会不会只做一件事好些。实际上&#xff0c;关键在于动力&#xff0c;我不可能每次都准备充分。动力&#xff0c;分配&#xff0c;这是目前进入大学我正在学…

用户至上!探索7种常用的用户体验研究方法

用户体验研究是产品开放过程中的重要组成部分&#xff0c;优秀的产品设计与高质量的用户体验是不可分割的。对于产品开发&#xff0c;选择合适的用户体验研究方法在很大程度上决定了产品的使用效果。本文全面阐述了用户体验研究、用户体验研究的重要性和用户体验研究方法&#…

138.随机链表的复制

/*** Definition for a Node.* struct Node {* int val;* struct Node *next;* struct Node *random;* };*/ typedef struct Node Node; struct Node* copyRandomList(struct Node* head) {Node* curhead;//拷贝节点插入到原节点后面while(cur){Node* copy(Node*)m…

产业项目招商活动会议课程报名签到h5小程序pc开源版开发

产业项目招商活动会议课程报名签到h5小程序pc开源版开发 一个集PC和移动端功能于一体的解决方案&#xff0c;线上线下进行服务&#xff0c;围绕 活动报名、在线课程、项目大厅、线下签到、会员系统等。为商会提供了更加便捷高效的管理方式&#xff0c;提升了商会活动和项目的组…

IaC实战指南:DevOps的自动化基石

基础设施即代码&#xff08;Infrastructure as Code&#xff0c;IaC&#xff09;是指利用脚本、配置或编程语言创建和维护基础设施的一组实践和流程。通过IaC&#xff0c;我们可以轻松测试各个组件、实现所需的功能并在最小化停机时间的前提下进行扩展。更值得一提的是&#xf…

学习通下载PDF资源

今天突然发现&#xff0c;学习通的pdf资源居然是没有下载入口的&#xff0c;这整的我想cv一下我的作业都搞不了&#xff0c;于是我一怒之下&#xff0c;怒了一下。 可以看到学习通的pdf资源是内嵌在网页的&#xff0c;阅读起来很不方便&#xff0c;虽然他内置了阅读器&#xf…

游泳耳机哪个牌子好性价比高?优选四大品质卓越品牌,诚挚推荐

随着科技的日新月异&#xff0c;游泳与音乐的融合已不再是遥不可及的梦想&#xff0c;一款性价比高的游泳耳机成为了许多游泳爱好者提升运动体验、激发运动热情的必备工具。可面对市场上种类繁多、价格各异的游泳耳机品牌&#xff0c;如何在确保高品质的同时&#xff0c;寻觅到…

使用antiSMASH数据库及软件分析微生物组

Introduction 上次简要介绍过了微生物组中生物合成基因簇&#xff08;BGCs&#xff09;分析&#xff0c;这次具体讲解使用antiSMASH数据库及软件分析的流程。 antiSMASH&#xff08;antibiotics & Secondary Metabolite Analysis Shell&#xff09;是一个用于识别和分析微…

解决“您的连接不是私密连接”

目录 那么为什么会出现这样提示呢 https访问有什么不同 将http访问更改为https访问 当您在浏览网页时&#xff0c;遇到“您的连接不是私密连接”的提示&#xff0c;这通常表示浏览器认为您的连接不够安全。这是因为浏览器无法信任网站使用的SSL证书&#xff0c;或者网站没有…

分布式光纤测温DTS的测温范围是多少?

分布式光纤测温DTS的测温范围不仅仅取决于光缆的感温能力&#xff0c;还受到多种复杂因素的影响。尽管高温光缆可以耐高温&#xff0c;低温光缆可以耐低温&#xff0c;甚至镀金光缆能够耐受高达700摄氏度的极高温度&#xff0c;然而&#xff0c;这些因素并不能完全解释测温范围…

【源码】[第二版]亲测完美双端获取通讯录、相册、短信定位源码

这套跟前面发的那套差不多&#xff0c;UI不一样而已。这套带了配套视频搭建教程&#xff0c;省的有些人问怎么搭建。 这套只能用HB打包&#xff0c;别问能不能获取苹果&#xff0c;ios短信谁也获取不了&#xff0c;ios相册的话自己研究下HB开权限。 还是推荐这套直接反编译改…

重大消息!软考高级论文单考,综合和案例连考

依据辽宁省信息技术教育中心&#xff08;辽宁省软考办&#xff09;发布《关于2024年上半年计算机技术与软件专业技术资格(水平)考试批次安排的通知》可知&#xff0c;2024年上半年软考有如下调整&#xff1a; 1.软考高级考试中&#xff0c;综合知识和案例分析连考&#xff08;…

性能测试 | 性能工具你用对了吗?

导读 要对客户端应用程序进行性能测试&#xff0c;需要了解用户场景和性能目标&#xff0c;选择合适的工具或方法来衡量和改进性能。其中涉及的性能指标有很多&#xff0c;如cpu使用率、内存、磁盘IO&#xff0c;相对应的性能观察工具也层出不穷&#xff0c;面对这些工具我们该…

24_Scala集合Map

文章目录 Scala集合Map1.构建Map2.增删改查3.Map的get操作细节 Scala集合Map –默认immutable –概念和Java一致 1.构建Map –创建kv键值对 && kv键值对的表达 –创建immutable map –创建mutable map //1.1 构建一个kv键值对 val kv "a" -> 1 print…

嵌入式Linux开发如何查看应用所链接的动态库

在开发中我们常常需要查看一个应用究竟链接了哪些对应的动态库 桌面linux的使用方法不赘述&#xff0c;网上资料有很多&#xff0c;对于嵌入式linux开发中&#xff0c;我们在ubuntu中使用ldd 是不行的 应该使用

全面升级企业网络安全 迈入SASE新时代

随着数字化业务、云计算、物联网和人工智能等技术的飞速发展&#xff0c;企业的业务部署环境日渐多样化&#xff0c;企业数据的存储由传统的数据中心向云端和SaaS迁移。远程移动设备办公模式的普及&#xff0c;企业多分支机构的加速设立&#xff0c;也使得企业业务系统的用户范…

数据结构-线性表-应用题-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; } …