掌握依赖倒置原则:使用 DI 实现干净代码的最佳实践
大家好,今天本人给大家带来文章《掌握依赖倒置原则:使用 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(); } }
正如我们所见,像 orderservice 和 productservice 这样的依赖关系在类构造函数中紧密耦合。这种直接依赖使得替换或模拟这些组件变得困难,这在测试或交换实现时提出了挑战。
依赖注入(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 对象以进行测试。关键是两种实现共享相同的接口,确保灵活性和可互换性。
控制反转 (ioc) 和服务定位器
为了将这一原则付诸实践,我们经常依赖一种称为控制反转 (ioc) 的模式。 ioc 是一种技术,将对依赖项的创建和管理的控制从类本身转移到外部组件。这通常是通过依赖注入容器或服务定位器来实现的,它充当一个注册表,我们可以从中请求所需的依赖项。通过 ioc,我们可以动态地注入适当的依赖项,而无需将它们硬编码到类构造函数中,从而使系统更加模块化并且更易于维护。
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 有一些缺点。一项主要限制是设置和管理依赖项所需的样板代码量。此外,它通常需要以特定的方式构建应用程序,这可能并不适合每个项目。
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 类,这将使容器能够在应用程序启动时管理生命周期和所有依赖项的注入。
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 实现干净代码的最佳实践》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!

- 上一篇
- 如何有效地运用电脑进行高效工作与生活管理

- 下一篇
- 如何使用 CSS 实现图片和文本水平居中对齐,并且文本换行?
-
- 文章 · 前端 | 8分钟前 |
- JavaScript检测内存泄漏的实用技巧
- 429浏览 收藏
-
- 文章 · 前端 | 29分钟前 |
- JavaScript本地存储(localStorage)实战攻略
- 474浏览 收藏
-
- 文章 · 前端 | 53分钟前 |
- HTML链接点击后颜色设置技巧
- 316浏览 收藏
-
- 文章 · 前端 | 11小时前 | JavaScript 错误处理 性能优化 动态加载 脚本加载顺序
- JavaScript动态加载JS文件的实用技巧
- 376浏览 收藏
-
- 文章 · 前端 | 11小时前 |
- JavaScript自定义元素及实现方法详解
- 253浏览 收藏
-
- 文章 · 前端 | 11小时前 | 网页加载速度 JavaScript压缩 UglifyJS Terser 手动优化
- JavaScript代码压缩技巧与实现攻略
- 495浏览 收藏
-
- 文章 · 前端 | 11小时前 |
- Array.from在JavaScript中的妙用与应用
- 192浏览 收藏
-
- 文章 · 前端 | 11小时前 |
- JavaScript防抖与节流实用技巧
- 308浏览 收藏
-
- 文章 · 前端 | 11小时前 | 表单数据 document.getElementById document.querySelector FormData elements属性
- JavaScript获取表单数据的实用技巧
- 212浏览 收藏
-
- 文章 · 前端 | 11小时前 |
- JavaScript创建链表的简易教程
- 257浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- PPTFake答辩PPT生成器
- PPTFake答辩PPT生成器,专为答辩准备设计,极致高效生成PPT与自述稿。智能解析内容,提供多样模板,数据可视化,贴心配套服务,灵活自主编辑,降低制作门槛,适用于各类答辩场景。
- 3次使用
-
- Lovart
- SEO摘要探索Lovart AI,这款专注于设计领域的AI智能体,通过多模态模型集成和智能任务拆解,实现全链路设计自动化。无论是品牌全案设计、广告与视频制作,还是文创内容创作,Lovart AI都能满足您的需求,提升设计效率,降低成本。
- 3次使用
-
- 美图AI抠图
- 美图AI抠图,依托CVPR 2024竞赛亚军技术,提供顶尖的图像处理解决方案。适用于证件照、商品、毛发等多场景,支持批量处理,3秒出图,零PS基础也能轻松操作,满足个人与商业需求。
- 26次使用
-
- PetGPT
- SEO摘要PetGPT 是一款基于 Python 和 PyQt 开发的智能桌面宠物程序,集成了 OpenAI 的 GPT 模型,提供上下文感知对话和主动聊天功能。用户可高度自定义宠物的外观和行为,支持插件热更新和二次开发。适用于需要陪伴和效率辅助的办公族、学生及 AI 技术爱好者。
- 24次使用
-
- 可图AI图片生成
- 探索快手旗下可灵AI2.0发布的可图AI2.0图像生成大模型,体验从文本生成图像、图像编辑到风格转绘的全链路创作。了解其技术突破、功能创新及在广告、影视、非遗等领域的应用,领先于Midjourney、DALL-E等竞品。
- 50次使用
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览
-
- UI设计中为何选择绝对定位的智慧之道
- 2024-02-03 501浏览