菜单

依赖倒置原则

相关源文件

目的与范围

本文档将解释依赖倒置原则 (DIP),它是 SOLID 设计原则的首字母缩略词中的第五个原则。DIP 侧重于模块之间应如何相互依赖,特别是提倡抽象优于具体实现。该原则对于创建可维护、可测试且松耦合的 JavaScript 代码至关重要。

有关其他 SOLID 原则的信息,请参阅

来源: README.md1729-1738

核心概念

依赖倒置原则包含两条基本规则

  1. 高层模块不应依赖低层模块。两者都应依赖抽象。
  2. 抽象不应依赖于细节。细节应依赖于抽象。

简而言之,DIP 鼓励我们通过让模块依赖于抽象接口而不是具体实现来解耦模块。这种方法提高了灵活性、可测试性和可维护性。

在缺乏正式接口的 JavaScript 中,这些抽象形式为“隐式契约”——本质上是对象之间暴露的函数和属性。

来源: README.md1729-1747

高层模块 vs. 低层模块

高层模块: 实现业务规则或特定于应用程序逻辑的组件。它们定义了系统的“做什么”。

低层模块: 实现数据访问、网络通信或系统资源等操作的组件。它们定义了系统的“如何做”。

没有 DIP,高层模块会直接依赖低层模块,造成紧耦合。

来源: README.md1732-1737

图示: 传统依赖 vs. DIP

来源: README.md1729-1742

与依赖注入的关系

虽然依赖倒置原则和依赖注入 (DI) 相关,但它们并非完全相同

  • DIP 关于通过抽象解耦模块
  • DI 是一种通过外部提供依赖项来实现 DIP 的技术

DI 是实现 DIP 的一种方式,因为它允许模块通过其依赖项进行配置,而不是直接创建它们。

来源: README.md1738-1744

JavaScript 实现

JavaScript 没有像静态类型语言那样正式的接口。相反,DIP 通过以下方式实现

  1. 隐式契约(预期的函数和属性)
  2. 依赖注入
  3. 鸭子类型(如果它看起来像鸭子,叫起来像鸭子,那它就是一只鸭子)

这种实现侧重于对象暴露的行为,而不是它们的具体类型。

来源: README.md1746-1750

示例: 库存系统

让我们通过一个代码库中的实际示例来了解它,该示例展示了 DIP 的不合规和合规实现。

不良示例 (违反 DIP)

在此示例中,InventoryTracker 类直接创建了 InventoryRequester 的实例,造成了紧耦合

这违反了 DIP,因为

  1. 高层模块 (InventoryTracker) 直接依赖低层模块 (InventoryRequester)
  2. 更改请求实现需要修改 InventoryTracker

来源: README.md1752-1783

良好示例 (遵循 DIP)

此重构示例将请求者作为依赖项注入

这遵循 DIP,因为

  1. 高层模块 (InventoryTracker) 依赖于抽象(“请求者”接口)
  2. 可以在不更改 InventoryTracker 的情况下替换多种实现
  3. 无需修改现有代码即可添加新的请求方法

来源: README.md1785-1828

图示: DIP 在库存系统中的实现

来源: README.md1785-1828

应用 DIP 的好处

  1. 降低耦合: 模块依赖于抽象而不是具体实现
  2. 提高可测试性: 依赖项可以轻松模拟或替换以进行测试
  3. 增强灵活性: 可以添加新实现而不更改现有代码
  4. 更轻松的重构: 对实现细节的更改不会影响更高层模块
  5. 更好的可伸缩性: 系统可以通过抽象的新实现来发展

来源: README.md1742-1744

JavaScript 中应用 DIP 的最佳实践

  1. 定义清晰的接口: 记录构成“契约”的预期函数和属性
  2. 使用依赖注入: 将依赖项传递给构造函数或方法,而不是在内部创建它们
  3. 避免在类中使用 new: 在外部创建实例并注入它们
  4. 关注行为而非实现: 设计围绕依赖项做什么,而不是它们如何做
  5. 保持抽象简单: 接口应集中且内聚
  6. 使用工厂函数或 IoC 容器: 用于更复杂的依赖管理

图示: DIP 在大型系统架构中的应用

来源: README.md1729-1750

实际应用示例

考虑一个场景,您需要从 HTTP 请求切换到 WebSockets 甚至本地缓存。使用 DIP

  1. 为数据检索定义一个通用接口(例如,requestItem(item)
  2. 为每个数据源(HTTP、WebSockets、缓存)实现此接口
  3. 使用合适的实现配置您的应用程序
  4. 无需修改业务逻辑即可更改实现

这种方法确保您的核心业务逻辑保持稳定,而实现细节可以不断发展。

常见陷阱

  1. 过度抽象: 创建不必要的抽象会增加复杂性
  2. 泄露的抽象: 当实现细节通过接口泄露时
  3. 僵化的接口: 设计过于针对特定实现的接口
  4. 忽略 JavaScript 的动态特性: 不必要地尝试强制执行静态类型模式

总结

依赖倒置原则鼓励设计组件依赖于抽象而不是具体实现的系统。在 JavaScript 中,这意味着在模块之间定义清晰的隐式契约,并使用依赖注入来提供实现。遵循 DIP 通过减少模块之间的耦合,可以实现更易于维护、可测试和灵活的代码。

来源: README.md1729-1828