0

0

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

DDD

DDD

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

|

392人浏览过

|

来源于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();
 }
}

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

依赖注入(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 实现干净代码的最佳实践

ONLYOFFICE
ONLYOFFICE

用ONLYOFFICE管理你的网络私人办公室

下载

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工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

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

419

2023.08.07

json是什么
json是什么

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

535

2023.08.23

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

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

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

52

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1133

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

java入门学习合集
java入门学习合集

本专题整合了java入门学习指南、初学者项目实战、入门到精通等等内容,阅读专题下面的文章了解更多详细学习方法。

1

2026.01.29

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

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

共21课时 | 1.1万人学习

TypeScript-45分钟入门
TypeScript-45分钟入门

共6课时 | 0.5万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号