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

如果您熟悉面向对象编程,或者刚刚开始探索它,您可能遇到过缩写词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 实现干净代码的最佳实践

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

登录后复制

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

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

在此示例中,我们将 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();

登录后复制

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

以上就是掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2648204.html

(0)
上一篇 2025年3月7日 08:08:20
下一篇 2025年2月25日 15:17:11

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • OST 掌握 JavaScript 的重要 JS 概念

    JavaScript 是一种多功能且功能强大的语言,对于现代 Web 开发至关重要。要精通 JavaScript,理解其一些核心概念至关重要。这些概念不仅有助于编写高效且可维护的代码,还使开发人员能够构建复杂且动态的 Web 应用程序。在本…

    2025年3月7日
    200
  • 掌握 JavaScript:利用高阶流释放函数响应式编程的力量

    javascript 中使用高阶流的函数响应式编程 (frp) 是处理代码中复杂的、基于时间的交互的强大方法。这是一种将我们的程序视为一系列数据流,而不是一系列命令式命令的方式。 让我们首先了解什么是流。在 frp 中,流是随时间变化的值序…

    2025年3月7日
    200
  • Javascript 中的符号及其示例

    symbol 是一个内置对象,其构造函数返回一个 symbol 基元 — 也称为 symbol 值 或只是一个 symbol — 保证是唯一的。符号通常用于向对象添加唯一的属性键,这些属性键不会与任何其他代码可能添加到该对象的键发生冲突,并…

    2025年3月7日
    200
  • Logging System with Proxy and Fetch

    代理对象:fetchlogger 包装了 fetch 函数。它使用 apply trap 来拦截对 fetch 的调用。 请求日志记录:记录请求的 url 和选项。响应处理:记录响应状态、状态文本和 url。克隆响应以确保正文可以被多次读取…

    2025年3月7日
    200
  • 如何将交互式图表和图形添加到 Tailwind CSS 管理模板

    管理仪表板模板对于有效管理和可视化数据至关重要。 tailwind css 以其实用性优先的方法而闻名,它简化了设计令人惊叹的管理仪表板的过程。向这些仪表板添加交互式图表和图形可以将原始数据转换为富有洞察力的可视化效果,从而增强整体用户体验…

    2025年3月7日 编程技术
    200
  • typescript定义接口教程

    接口是在 TypeScript 中定义对象或类属性和方法的形状的语法结构。通过使用 interface 关键字来定义接口,TypeScript 编译器可以检查代码是否遵守这些约定,从而提高代码的可读性、静态类型检查和可重用性。 TypeSc…

    2025年3月7日
    200
  • typescript接口数组

    TypeScript 中使用接口数组可以为数组元素定义特定类型,确保类型安全和代码可读性。接口数组的创建、访问元素以及推进类型均有特定的语法和注意事项。 TypeScript 中使用接口数组 TypeScript 接口是用于定义对象的类型,…

    2025年3月7日
    200
  • typescript 类型声明

    TypeScript 类型声明是描述变量、函数和类类型的工具,包括显式声明和类型推断。类型声明可提高代码可读性,增强 IDE 支持,改善代码可靠性,并提高可重用性。在 TypeScript 中使用类型声明很简单,只需在变量、函数或类定义之前…

    2025年3月7日
    200
  • typescript 变量类型

    TypeScript 中存在变量类型,它定义变量值类型。这些类型包括基本类型(如 number、string、boolean)、复合类型(如 array、tuple、enum)和引用类型(如 class、interface)。变量类型可以通…

    2025年3月7日
    200
  • typescript高级类型声明

    TypeScript 高级类型声明是一组高级功能,用于定义复杂和可重用的类型,从而增强代码的可读性、可维护性和可重用性。包括:1. 类型别名;2. 交叉类型;3. 联合类型;4. 元组类型;5. 枚举类型;6. 泛型类型;7. 条件类型;8…

    2025年3月7日
    200

发表回复

登录后才能评论