菜单

开闭原则

相关源文件

目的与范围

本文档解释了开放/封闭原则(OCP),SOLID 原则中的第二条原则,及其在 JavaScript 中的应用。开放/封闭原则指出,软件实体应“对扩展开放,对修改关闭”。本页面涵盖了针对 JavaScript 的实现模式、示例和最佳实践。有关其他 SOLID 原则的信息,请参阅 SOLID 原则或具体原则:单一职责原则里氏替换原则接口隔离原则依赖倒置原则

定义和核心概念

开放/封闭原则最初由 Bertrand Meyer 提出,它指出:

软件实体(类、模块、函数等)应“对扩展开放,对修改关闭”。

这意味着:

  • 对扩展开放:您应该能够在不修改其源代码的情况下为现有代码添加新功能。
  • 对修改关闭:一旦代码被建立、测试并投入生产,当需求发生变化时,就不应需要修改它。

在 JavaScript 中,由于该语言的动态特性以及缺乏严格的接口或类型检查,该原则尤为重要。

来源:README.md1441-1447

JavaScript 中的实现

在 JavaScript 中,开放/封闭原则可以通过多种技术来实现:

  1. 继承:扩展基类以添加功能。
  2. 组合:构建可以以不同方式组合的组件。
  3. 高阶函数:使用将其他函数作为参数的函数。
  4. 配置文件对象:使用具有控制行为的属性的对象。
  5. 插件架构:创建允许通过插件添加新功能的系统。

图示:开放/封闭原则实现模式

来源:README.md1441-1447

案例研究:HTTP 请求适配器

README 提供了一个关于 OCP 违规和纠正的绝佳示例,涉及 HTTP 请求适配器。

违反开放/封闭原则

在有问题的实现中,HttpRequester 类直接检查适配器的名称属性,并为每种适配器类型包含不同的逻辑。

这违反了开放/封闭原则,因为:

  1. 要添加新的适配器类型(例如 fetchAdapter),我们需要修改 HttpRequester 类。
  2. HttpRequester 必须了解每种适配器的实现细节。
  3. 适配器实现的更改可能需要更改 HttpRequester

来源:README.md1465-1489

遵循开放/封闭原则

改进后的实现通过让每个适配器实现一个通用接口来遵循 OCP。

此设计遵循 OCP,因为:

  1. HttpRequester 已关闭修改——添加新适配器时无需更改它。
  2. 系统对扩展开放——可以通过创建实现 request 方法的新类来添加新适配器。
  3. 每个适配器都封装了自己的实现细节。

来源:README.md1494-1527

图示:HTTP 请求者 OCP 前后

来源:README.md1465-1527

实现模式

1. 基于继承的扩展

在 JavaScript 中,在不修改基类行为的情况下扩展类是一种常见的 OCP 模式。

来源:README.md1441-1447 README.md1494-1527

2. 策略模式

策略模式是 JavaScript 中实现 OCP 的另一种方式。

此模式允许在不修改上下文类的情况下添加新策略,类似于上面的 HTTP 适配器示例。

来源:README.md1494-1527

JavaScript 中 OCP 的最佳实践

要有效地应用开放/封闭原则于 JavaScript,请注意以下几点:

  1. 为扩展设计:识别系统中易变的部分,并设计它们以使其可扩展。
  2. 使用抽象:创建扩展必须遵守的抽象接口(在 JavaScript 中是隐式的)。
  3. 应用依赖注入:将依赖项传递给组件,而不是硬编码它们。
  4. 组合优于继承:当组合比继承提供更多灵活性时,请考虑使用组合。
  5. 避免类型检查:与其检查类型并进行分支,不如使用多态性。
反模式更好的方法
带 if/else 分支的类型检查多态方法
硬编码的依赖项依赖注入
直接修改现有类通过继承或组合进行扩展
组件之间的紧耦合通过接口实现松耦合
基于类型的 switch 语句策略模式

来源:README.md1441-1447 README.md1494-1527

OCP 和其他 SOLID 原则

开放/封闭原则与其他 SOLID 原则协同工作。

  1. 单一职责原则:具有单一职责的类更容易扩展而不进行修改。
  2. 里氏替换原则:正确的继承层级确保通过继承进行的扩展不会破坏现有功能。
  3. 接口隔离原则:更小、更专注的接口更容易创建新实现。
  4. 依赖倒置原则:依赖抽象而不是具体实现,更容易扩展系统。

图示:OCP 和 SOLID 的关系

来源:README.md1379-1828

JavaScript 中的常见挑战

在 JavaScript 中实现 OCP 存在一些独特的挑战:

  1. 缺乏接口:JavaScript 没有正式的接口,因此组件之间的契约必须通过文档或测试来强制执行。
  2. 动态类型:在运行时进行类型检查可能很有诱惑力,但这会违反 OCP。
  3. 原型污染:扩展内置对象可能导致冲突。
  4. 模块系统:JavaScript 的模块系统需要仔细设计才能允许扩展。

解决方案

  1. 使用 TypeScript 或 JSDoc 注释来记录隐式接口。
  2. 在适当的情况下,创建带有默认实现的基类。
  3. 使用组合和高阶函数来允许扩展。
  4. 设计模块以公开扩展点。

来源:README.md1441-1447 README.md1494-1527

结论

开放/封闭原则是一项基本的设计指南,有助于创建更易于维护和更灵活的 JavaScript 代码。通过设计对扩展开放但对修改关闭的组件,您可以:

  1. 降低破坏现有功能的风险。
  2. 使您的代码库更具模块化和可维护性。
  3. 允许未来的需求而无需重写代码。
  4. 创建更易于测试的组件。

遵循 OCP 需要一些初步的设计工作,但从长远来看,通过提高代码稳定性和适应性,它会带来丰厚的回报。