讲师中心 微信公众号
AI工具推荐 视频效率加速

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

胖婷君_7326

胖婷君_7326

发布时间:2024-11-29 08:27:01

|

452人浏览过

|

来源于dev.to

转载

如果您熟悉面向对象编程,或者刚刚开始探索它,您可能遇到过缩写词solid。 solid 代表了一组旨在帮助开发人员编写干净、可维护和可扩展代码的原则。在这篇文章中,我们将重点关注 solid 中的“d”,它代表依赖倒置原则。

但在深入了解细节之前,让我们首先花点时间了解这些原则背后的“原因”。

在面向对象编程中,我们通常将应用程序分解为类,每个类封装特定的业务逻辑并与其他类交互。例如,想象一个简单的在线商店,用户可以将产品添加到购物车中。此场景可以通过多个类一起进行建模来管理商店的运营。让我们以这个例子为基础来探索依赖倒置原则如何改进我们系统的设计。

class productservice {
 getproducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class orderservice {
 constructor() {
   this.productservice = new productservice();
 }

 getordersforuser() {
   return this.productservice.getproducts();
 }
}


class userservice {
 constructor() {
   this.orderservice = new orderservice();
 }

 getuserorders() {
   return this.orderservice.getordersforuser();
 }
}

正如我们所见,像 orderservice 和 productservice 这样的依赖关系在类构造函数中紧密耦合。这种直接依赖使得替换或模拟这些组件变得困难,这在测试或交换实现时提出了挑战。

依赖注入(di)

依赖注入 (di) 模式提供了这个问题的解决方案。通过遵循 di 模式,我们可以解耦这些依赖关系,并使我们的代码更加灵活和可测试。以下是我们如何重构代码来实现 di:

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

class productservice {
 getproducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class orderservice {
 constructor(private productservice: productservice) {}

 getordersforuser() {
   return this.productservice.getproducts();
 }
}


class userservice {
 constructor(private orderservice: orderservice) {}

 getuserorders() {
   return this.orderservice.getordersforuser();
 }
}


new userservice(new orderservice(new productservice()));

我们显式地将依赖项传递给每个服务的构造函数,这虽然是朝着正确方向迈出的一步,但仍然会导致紧密耦合的类。这种方法确实稍微提高了灵活性,但它并没有完全解决使我们的代码更加模块化且易于测试的根本问题。

依赖倒置原理(dip)

依赖倒置原理(dip)通过回答关键问题更进一步:我们应该传递什么?该原则表明,我们不应传递具体的实现,而应仅传递必要的抽象,特别是与预期接口匹配的依赖项。

例如,考虑带有 getproducts 方法的 productservice 类,该方法返回 产品数组 。我们可以通过多种方式实现它,而不是直接将 productservice 耦合到特定的实现(例如,从数据库中获取数据)。一种实现可能从数据库获取产品,而另一种实现可能返回硬编码的 json 对象以进行测试。关键是两种实现共享相同的接口,确保灵活性和可互换性。

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

控制反转 (ioc) 和服务定位器

为了将这一原则付诸实践,我们经常依赖一种称为控制反转 (ioc) 的模式。 ioc 是一种技术,将对依赖项的创建和管理的控制从类本身转移到外部组件。这通常是通过依赖注入容器或服务定位器来实现的,它充当一个注册表,我们可以从中请求所需的依赖项。通过 ioc,我们可以动态地注入适当的依赖项,而无需将它们硬编码到类构造函数中,从而使系统更加模块化并且更易于维护。

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

class servicelocator {
 static #modules = new map();

 static get(modulename: string) {
   return servicelocator.#modules.get(modulename);
 }

 static set(modulename: string, exp: never) {
   servicelocator.#modules.set(modulename, exp);
 }
}

class productservice {
 getproducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class orderservice {
 constructor() {
   const productservice = servicelocator.get('productservice');
   this.productservice = new productservice();
 }

 getordersforuser() {
   return this.productservice.getproducts();
 }
}


class userservice {
 constructor() {
   const orderservice = servicelocator.get('orderservice');
   this.orderservice = new orderservice();
 }

 getuserorders() {
   return this.orderservice.getordersforuser();
 }
}

servicelocator.set('productservice', productservice);
servicelocator.set('orderservice', orderservice);


new userservice();

正如我们所看到的,依赖项是在容器内注册的,这使得它们可以在必要时被替换或交换。这种灵活性是一个关键优势,因为它促进了组件之间的松散耦合。

但是,这种方法有一些缺点。由于依赖项是在运行时解析的,因此如果出现问题(例如,如果依赖项丢失或不兼容),可能会导致运行时错误。此外,无法保证注册的依赖项将严格符合预期的接口,这可能会导致微妙的问题。这种依赖关系解析方法通常称为服务定位器模式,并且在许多情况下被认为是反模式,因为它依赖于运行时解析并且有可能掩盖依赖关系。

inversifyjs

javascript 中用于实现 控制反转 (ioc) 模式的最流行的库之一是 inversifyjs。它提供了一个强大且灵活的框架,用于以干净、模块化的方式管理依赖关系。然而,inversifyjs 有一些缺点。一项主要限制是设置和管理依赖项所需的样板代码量。此外,它通常需要以特定的方式构建应用程序,这可能并不适合每个项目。

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

Scrapfly SDK TypeScript
Scrapfly SDK TypeScript

使用 Scrapfly SDK(scrapfly-sdk)在 TypeScript 或 JavaScript 中进行网页抓取,用于用户请求抓取 URL、绕过反爬虫防护、渲染页面等场景。

下载

inversifyjs 的替代方案是friendly-di,这是一种轻量级且更简化的方法,用于管理 javascript 和 typescript 应用程序中的依赖关系。它的灵感来自于 angular 和 nestjs 等框架中的 di 系统,但设计得更加简约、简洁。

friendly-di 的一些主要优势包括:

  • 体积小:只有 2 kb,没有外部依赖。
  • 跨平台:在浏览器和 node.js 环境中无缝工作。
  • 简单的 api:直观且易于使用,只需最少的配置。
  • mit 许可证:具有宽松许可的开源。

但是,需要注意的是,friendly-di 是专为 typescript 设计的,您需要先安装其依赖项才能开始使用它。

npm i friendly-di reflect-metadata

并且还扩展tsconfig.json:

{
 "compileroptions": {
   "experimentaldecorators": true,
   "emitdecoratormetadata": true
 }
}

上面的例子可以用friendly-di修改:

import 'reflect-metadata';
import { injectable } from 'friendly-di';

@injectable()
class productservice {
 getproducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}

@injectable()
class orderservice {
 constructor(private productservice: productservice) {}

 getordersforuser() {
   return this.productservice.getproducts();
 }
}

@injectable()
class userservice {
 constructor(private orderservice: orderservice) {}

 getuserorders() {
   return this.orderservice.getordersforuser();
 }
}

@injectable()
class app {
 constructor(private userservice: userservice) {}


 run() {
   return this.userservice.getuserorders();
 }
}
  1. 正如我们所看到的,我们添加了 @injectable() 装饰器,它将我们的类标记为可注入的,表明它们是依赖注入系统的一部分。这个装饰器允许 di 容器知道这些类可以在需要的地方实例化和注入。

  2. 当在构造函数中将类声明为依赖项时,我们不会直接绑定到具体类本身。相反,我们根据其接口来定义依赖关系。这将我们的代码与具体实现解耦,并提供更大的灵活性,从而在需要时更容易交换或模拟依赖项。

  3. 在此示例中,我们将 userservice 放置在 app 类中。这种模式被称为组合根。 组合根是应用程序中组装和注入所有依赖项的中心位置 - 本质上是我们应用程序依赖关系图的“根”。通过将此逻辑保留在一个位置,我们可以更好地控制如何在整个应用程序中解析和注入依赖项。

最后一步是在 di 容器中注册 app 类,这将使容器能够在应用程序启动时管理生命周期和所有依赖项的注入。

掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践

import { container } from 'friendly-di';

const app = new container(app).compile();

app.run();

如果我们需要替换应用程序中的任何类,我们只需要按照原始接口创建模拟类:

@injectable()
class mockproductservice {
 getproducts() {
   return ['new product 1', 'new product 2', 'new product 3'];
 }
}

然后使用替换方法,我们将可替换类声明为模拟类:

import { container } from 'friendly-di';

const app = new container(app)
 .replace(productservice, mockproductservice)
 .compile();

app.run();

友好-di我们可以多次替换:

const app = new Container(App)
 .replace(ProductService, MockProductService)
 .replace(OrderService, MockOrderService)
 .compile();

app.run();

就这样,如果您对此主题有任何意见或澄清,请在评论中写下您的想法。

热门AI工具

更多
火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

332

2026.02.13

TypeScript全栈项目架构与接口规范设计
TypeScript全栈项目架构与接口规范设计

本专题面向全栈开发者,系统讲解基于 TypeScript 构建前后端统一技术栈的工程化实践。内容涵盖项目分层设计、接口协议规范、类型共享机制、错误码体系设计、接口自动化生成与文档维护方案。通过完整项目示例,帮助开发者构建结构清晰、类型安全、易维护的现代全栈应用架构。

540

2026.02.25

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

391

2026.03.13

TypeScript 全栈开发进阶指南
TypeScript 全栈开发进阶指南

面向有 JavaScript 基础的开发者,深入讲解 TypeScript 的类型系统与全栈开发实践。

306

2026.06.03

TypeScript Node.js 全栈工程化与Monorepo架构实践
TypeScript Node.js 全栈工程化与Monorepo架构实践

本专题围绕 TypeScript 在 Node.js 全栈开发中的工程化实践展开,系统讲解 Monorepo 架构设计、包管理策略、模块复用机制以及服务端与前端统一类型系统的构建方法。通过真实项目案例,帮助开发者提升大型全栈项目的可维护性与协作效率。

718

2026.06.16

json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2275

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

3662

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

1216

2023.10.13

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

20

2026.11.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.8万人学习

TypeScript——十天技能课堂
TypeScript——十天技能课堂

共21课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn