如何在Node.js里实现依赖注入

简介: 如何在Node.js里实现依赖注入

什么是依赖注入
依赖注入是一种用于在开发过程中实现控制反转(IoC)的技术。在IoC中,对程序流的控制是颠倒的:依赖项不是控制其依赖项的创建和管理的组件,而是从外部源提供给组件。

在传统的编程模式中,一个组件可能会直接创建并管理它所依赖的其他组件,这会导致组件之间的耦合度较高,难以维护和测试。

控制反转是一种设计原则,它改变了组件之间的控制关系。在IoC中,组件不再自己创建和管理它所依赖的组件,而是将这种控制权交给外部。具体来说,依赖注入是IoC的一种实现方式,它通过外部源(比如容器或框架)来提供组件所需的依赖项。

这样做的好处是:

解耦:组件不再直接依赖于具体的依赖项实现,而是依赖于抽象的接口或抽象类,这样可以降低组件之间的耦合度。
易于维护:由于组件之间的依赖关系是由外部控制的,因此修改一个组件的依赖项时,不需要修改组件本身的代码,只需要调整外部的配置或代码。
易于测试:在单元测试时,可以轻松地替换组件的依赖项为模拟对象(mock objects),从而可以独立地测试组件的功能。
可重用性:由于组件不直接依赖于具体的实现,而是依赖于抽象,这使得组件更容易在不同的上下文中被重用。
如何实现
了解完定义,我们来看一下案例。先看一个没有使用依赖注入的例子:

手动注入
// Dependency.js
class Dependency {
constructor() {
this.name = 'Dependency';
}
}

// Service.js
class Service {
constructor(dependency) {
this.dependency = dependency;
}
greet() {
console.log(Hello, I depend on ${this.dependency.name});
}
}
// App.js
const Dependency = require('./Dependency');
const Service = require('./Service');
const dependency = new Dependency();
const service = new Service(dependency);
service.greet();

1
这里展示了一个简单的依赖注入模式。Service依赖于dependency对象,在创建了Service类的实例时,将dependency实例作为参数传递给Service的构造函数,这样Service就依赖于Dependency。

自动注入
手动注入毕竟太麻烦,而且依赖的实例多的时候,每个都通过形参传入不太靠谱,下面我们来看看如何实现自动注入。

// Dependency.js
export class Dependency {
constructor() {
this.name = 'Dependency';
}
}

// Service.js
export class Service {
constructor(dependency) {
this.dependency = dependency;
}
greet() {
console.log(Hello, I depend on ${this.dependency.name});
}
}

// Container.js
import { Dependency } from './Dependency';
import { Service } from './Service';

export class Container {
constructor() {
this.dependencyInstances = new Map();
this.dependencyConstructors = new Map([
[Dependency, Dependency],
[Service, Service],
]);
}

getDependency(ctor) {
if (!this.dependencyInstances.has(ctor)) {
const dependencyConstructor = this.dependencyConstructors.get(ctor);
if (!dependencyConstructor) {
throw new Error(No dependency registered for ${ctor.name});
}
const instance = new dependencyConstructor(this.getDependency.bind(this));
this.dependencyInstances.set(ctor, instance);
}
return this.dependencyInstances.get(ctor);
}
}

// App.js
import { Container } from './Container';
import { Service } from './Service';
import { Dependency } from './Dependency';

const container = new Container();
const service = container.getDependency(Service);
service.greet();

1
这里增加了Container用于管理实例,我们只需要维护对应的依赖关系,在需要使用的时候再创建对应的实例。是不是很简单?简单才是王道,使用过egg的小伙伴都知道egg里只需要导出Class,我们就可以直接在context里访问对应的实例。

// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
async info() {
const { ctx } = this;
const userId = ctx.params.id;
const userInfo = await ctx.service.user.find(userId);
ctx.body = userInfo;
}
}
module.exports = UserController;

// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
async find(uid) {
// 假如我们拿到用户 id,从数据库获取用户详细信息
const user = await this.ctx.db.query(
'select * from user where uid = ?',
uid
);

// 假定这里还有一些复杂的计算,然后返回需要的信息
const picture = await this.getPicture(uid);

return {
  name: user.user_name,
  age: user.age,
  picture
};

}
}
module.exports = UserService;

1
egg里的实现其实更彻底,直接使用了getter替代了container.getDependency(Service),使用了本地文件读取加载class实例。其实现如下:

// define ctx.service
Object.defineProperty(app.context, property, {
get() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const ctx = this;
// distinguish property cache,
// cache's lifecycle is the same with this context instance
// e.x. ctx.service1 and ctx.service2 have different cache
if (!ctx[CLASS_LOADER]) {
ctx[CLASS_LOADER] = new Map();
}
const classLoader: Map = ctx[CLASS_LOADER];
let instance = classLoader.get(property);
if (!instance) {
instance = getInstance(target, ctx);
classLoader.set(property, instance!);
}
return instance;
},
});

1
优先从缓存里读取实例,不存在则执行getInstance,其实现如下:

function getInstance(values: any, ctx: ContextDelegation) {
// it's a directory when it has no exports
// then use ClassLoader
const Class = values[EXPORTS] ? values : null;
let instance;
if (Class) {
if (isClass(Class)) {
instance = new Class(ctx);
} else {
// it's just an object
instance = Class;
}
// Can't set property to primitive, so check again
// e.x. module.exports = 1;
} else if (isPrimitive(values)) {
instance = values;
} else {
instance = new ClassLoader({ ctx, properties: values });
}
return instance;
}

1
优先从缓存里加载,如果缓存不存在则主动去加载一次。

第三方库
除了自己实现之外,我们也可以借助第三方的库,如InversifyJS、Awilix等。这些库提供了更高级的功能,如依赖的自动解析、生命周期管理等。下面是使用InversifyJS的一个基本示例:

首先,安装InversifyJS:

npm install inversify reflect-metadata --save
1
然后,我们可以这样使用它:

const { injectable, inject, Container } = require('inversify');
require('reflect-metadata');

// 定义依赖
@injectable()
class Logger {
log(message) {
console.log(message);
}
}

@injectable()
class EmailService {
constructor(@inject(Logger) logger) {
this.logger = logger;
}

sendEmail(to, content) {
// 发送邮件的逻辑...
this.logger.log(Sending email to ${to});
}
}

// 设置容器
const container = new Container();
container.bind(Logger).toSelf();
container.bind(EmailService).toSelf();

// 从容器中获取实例
const emailService = container.get(EmailService);

// 使用服务
emailService.sendEmail('example@example.com', 'Hello, Dependency Injection with InversifyJS!');

1
在这个例子中,我们使用了InversifyJS的装饰器来标记Logger和EmailService是可注入的。我们还创建了一个Container来管理我们的依赖,然后从容器中获取了EmailService的实例。

总结
依赖注入是一个强大的模式,它可以帮助我们构建更加灵活、可维护和可测试的Node.js应用程序。无论是手动实现还是使用专门的库,依赖注入都值得在我们的工具箱中占有一席之地。通过将依赖注入作为应用程序架构的一部分,我们可以提高代码质量,并为未来的扩展打下坚实的基础。
原文链接:https://blog.csdn.net/zmh_fuhuasishui/article/details/142862489

相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
16天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
4天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
1天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
200 10
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
21天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2578 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
3天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
165 2
|
1天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
100 65
|
20天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1577 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
233 2