菜单

SOLID 原则

相关源文件

目的与范围

本文档概述了 SOLID 原则及其在 JavaScript 中的实现。SOLID 是五个设计原则的首字母缩写,它们有助于开发人员编写更易于维护、更灵活、更具可扩展性的代码。本页涵盖了每个原则的定义、在 JavaScript 中的应用以及实际示例。有关类的通用信息,请参阅 ,有关组合与继承的详细信息,请参阅 组合与继承

来源: README.md1379-1380

SOLID 原则简介

SOLID 原则是 Robert C. Martin 提出的五项设计原则,旨在创建更易于维护和扩展的软件。这些原则与语言无关,但可以有效地应用于 JavaScript 开发。正确实施 SOLID 原则有助于降低代码复杂性,提高可读性,并使代码库更易于扩展。

五项原则是

  • S:单一职责原则
  • O:开闭原则
  • L:里氏替换原则
  • I:接口隔离原则
  • D:依赖倒置原则

SOLID 原则概述图

来源: README.md1379-1380

单一职责原则 (SRP)

单一职责原则指出,一个类或模块应该只有一个引起它变化的原因。换句话说,一个类应该只有一个职责或任务。

核心概念

  • 每个类/模块处理单一关注点
  • 降低复杂性和耦合度
  • 使代码更易于维护和理解
  • 更易于测试和重构

示例实现

在下面的示例中,UserSettings 类同时处理用户设置和身份验证,违反了 SRP。

SRP 违规: UserSettings 类同时处理身份验证和设置更改。

符合 SRP: 职责被分离到两个类中

  • UserAuth:处理身份验证
  • UserSettings:处理设置操作,并使用 UserAuth 进行验证

来源: README.md1381-1437

开闭原则 (OCP)

开闭原则指出,软件实体(类、模块、函数)应该对扩展开放,但对修改关闭。这意味着您应该能够在不更改现有代码的情况下添加新功能。

核心概念

  • 在不修改源代码的情况下扩展行为
  • 使用抽象和继承
  • 通过接口、抽象类或多态性实现
  • 降低更改时的风险

示例实现

下图展示了演示开闭原则的 HTTP 请求处理

OCP 违规: HttpRequester 类会检查适配器类型,并为每种类型提供不同的实现,添加新适配器时需要修改。

符合 OCP

  • 基类 Adapter 定义了通用接口
  • 具体实现(AjaxAdapterNodeAdapter)提供特定实现
  • HttpRequester 可以与任何实现该接口的适配器一起使用
  • 可以添加新的适配器,而无需修改现有代码

来源: README.md1439-1530

里氏替换原则 (LSP)

里氏替换原则指出,超类对象的实例应该可以用子类对象的实例替换,而不影响程序的正确性。子类型必须可以替换其基类型。

核心概念

  • 子类型必须满足其基类型的契约
  • 在扩展类时保持预期的行为
  • 避免子类中出现意外的副作用
  • 保持前置条件和后置条件

矩形-正方形问题

该原则通常以经典的矩形-正方形问题来说明

LSP 违规: Square 类继承自 Rectangle,但改变了 setWidthsetHeight 的行为,当在需要 Rectangle 的地方使用 Square 时会导致意外结果。

符合 LSP: 不再继承,而是 RectangleSquare 都是通用 Shape 接口的独立实现,每个实现都有其适当的计算。

来源: README.md1532-1647

接口隔离原则 (ISP)

接口隔离原则指出,客户端不应被迫依赖它们不使用的接口。创建多个专门的接口比一个通用接口要好。

核心概念

  • 创建专注、特定的接口
  • 避免包含客户端不需要的方法的“臃肿”接口
  • 防止客户端实现不必要的方法
  • 在 JavaScript 中,这适用于对象之间的隐式契约

示例实现

下图展示了 DOM 遍历实现中的 ISP

ISP 违规: DOMTraverser 类要求客户端提供一个 animationModule,即使它们不需要动画功能。

符合 ISP: 改进的 DOMTraverser 实现通过选项对象使模块成为可选的,允许客户端仅包含它们需要的接口。

来源: README.md1649-1726

依赖倒置原则 (DIP)

依赖倒置原则指出,高层模块不应依赖于低层模块。两者都应依赖于抽象。此外,抽象不应依赖于细节;细节应依赖于抽象。

核心概念

  • 依赖于抽象,而不是具体实现
  • 高层模块定义低层模块实现的接口
  • 便于测试和组件替换
  • 通过依赖注入和接口实现

DIP 中组件之间的关系

示例实现

库存跟踪系统演示了 DIP

DIP 违规: InventoryTracker 直接实例化一个特定的 InventoryRequester 实现,造成了紧耦合。

符合 DIP

  • InventoryTracker 接受任何实现所需接口的请求者
  • 通过构造函数注入请求者,允许使用不同的实现
  • 使系统更具灵活性和可测试性

来源: README.md1728-1828

SOLID 原则的优点

在 JavaScript 开发中应用 SOLID 原则有许多好处

原则关键优势对代码的影响
单一职责提高可维护性具有专注职责的类/模块
开闭原则扩展时风险降低无需修改现有代码即可扩展
里氏替换原则可预测的行为子类型保持预期行为
接口隔离原则专注的客户端接口不依赖未使用的函数
依赖倒置降低耦合度高层策略独立于低层细节

总体益处

来源: README.md1379-1830

SOLID 原则的实践

虽然 SOLID 原则提供了宝贵的指导,但它们应在 JavaScript 中务实地应用

  • JavaScript 的动态特性和缺乏接口意味着某些原则(如 ISP)是通过隐式契约来应用的
  • 过度应用可能导致不必要的抽象和复杂性
  • 从简单的设计开始,随着复杂性的增长重构为 SOLID
  • 考虑完美遵守与实际解决方案之间的权衡

SOLID 与整洁代码的关系

来源: README.md1379-1830

有关 SOLID 相关整洁代码原则的进一步探讨