依赖倒置原则
相关源文件
目的与范围
本文档将解释依赖倒置原则 (DIP),它是 SOLID 设计原则的首字母缩略词中的第五个原则。DIP 侧重于模块之间应如何相互依赖,特别是提倡抽象优于具体实现。该原则对于创建可维护、可测试且松耦合的 JavaScript 代码至关重要。
有关其他 SOLID 原则的信息,请参阅
来源: README.md1729-1738
核心概念
依赖倒置原则包含两条基本规则
- 高层模块不应依赖低层模块。两者都应依赖抽象。
- 抽象不应依赖于细节。细节应依赖于抽象。
简而言之,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 通过以下方式实现
- 隐式契约(预期的函数和属性)
- 依赖注入
- 鸭子类型(如果它看起来像鸭子,叫起来像鸭子,那它就是一只鸭子)
这种实现侧重于对象暴露的行为,而不是它们的具体类型。
来源: README.md1746-1750
示例: 库存系统
让我们通过一个代码库中的实际示例来了解它,该示例展示了 DIP 的不合规和合规实现。
不良示例 (违反 DIP)
在此示例中,InventoryTracker 类直接创建了 InventoryRequester 的实例,造成了紧耦合
这违反了 DIP,因为
- 高层模块 (
InventoryTracker) 直接依赖低层模块 (InventoryRequester)
- 更改请求实现需要修改
InventoryTracker 类
来源: README.md1752-1783
良好示例 (遵循 DIP)
此重构示例将请求者作为依赖项注入
这遵循 DIP,因为
- 高层模块 (
InventoryTracker) 依赖于抽象(“请求者”接口)
- 可以在不更改
InventoryTracker 的情况下替换多种实现
- 无需修改现有代码即可添加新的请求方法
来源: README.md1785-1828
图示: DIP 在库存系统中的实现
来源: README.md1785-1828
应用 DIP 的好处
- 降低耦合: 模块依赖于抽象而不是具体实现
- 提高可测试性: 依赖项可以轻松模拟或替换以进行测试
- 增强灵活性: 可以添加新实现而不更改现有代码
- 更轻松的重构: 对实现细节的更改不会影响更高层模块
- 更好的可伸缩性: 系统可以通过抽象的新实现来发展
来源: README.md1742-1744
JavaScript 中应用 DIP 的最佳实践
- 定义清晰的接口: 记录构成“契约”的预期函数和属性
- 使用依赖注入: 将依赖项传递给构造函数或方法,而不是在内部创建它们
- 避免在类中使用
new: 在外部创建实例并注入它们
- 关注行为而非实现: 设计围绕依赖项做什么,而不是它们如何做
- 保持抽象简单: 接口应集中且内聚
- 使用工厂函数或 IoC 容器: 用于更复杂的依赖管理
图示: DIP 在大型系统架构中的应用
来源: README.md1729-1750
实际应用示例
考虑一个场景,您需要从 HTTP 请求切换到 WebSockets 甚至本地缓存。使用 DIP
- 为数据检索定义一个通用接口(例如,
requestItem(item))
- 为每个数据源(HTTP、WebSockets、缓存)实现此接口
- 使用合适的实现配置您的应用程序
- 无需修改业务逻辑即可更改实现
这种方法确保您的核心业务逻辑保持稳定,而实现细节可以不断发展。
常见陷阱
- 过度抽象: 创建不必要的抽象会增加复杂性
- 泄露的抽象: 当实现细节通过接口泄露时
- 僵化的接口: 设计过于针对特定实现的接口
- 忽略 JavaScript 的动态特性: 不必要地尝试强制执行静态类型模式
总结
依赖倒置原则鼓励设计组件依赖于抽象而不是具体实现的系统。在 JavaScript 中,这意味着在模块之间定义清晰的隐式契约,并使用依赖注入来提供实现。遵循 DIP 通过减少模块之间的耦合,可以实现更易于维护、可测试和灵活的代码。
来源: README.md1729-1828