菜单

单一职责原则

相关源文件

目的与范围

本文档解释了 SOLID 设计原则中的第一个原则——单一职责原则(SRP)及其在 JavaScript 中的实现。SRP 侧重于确保类或函数只有一个修改的原因。有关所有 SOLID 原则的更广泛背景,请参阅 SOLID 原则

定义

单一职责原则指出,一个类或模块应该只有一个修改的原因。这意味着一个类在软件系统中应该只有一个工作或职责。

来源: README.md1381-1387

单一职责的重要性

遵守 SRP 为代码质量和可维护性带来了多项好处

  1. 易于理解 - 当一个类只做一件事时,更容易理解
  2. 易于维护 - 对系统某一部分的更改不会影响不相关的代码
  3. 降低耦合度 - 专注的模块依赖更少
  4. 更好的可测试性 - 单一职责的模块更容易测试
  5. 更高的可重用性 - 定义清晰、专注的组件更有可能被重用

来源: README.md1387-1392

在 JavaScript 类中实现 SRP

识别违反行为

SRP 的常见违反行为是当一个类处理多个不同的关注点时。下面的示例展示了一个同时管理用户身份验证和用户设置的类

SRP 违反示例

来源: README.md1392-1409

正确的 SRP 实现

为了遵守 SRP,将类拆分成独立的类,每个类只有一个职责

符合 SRP 的解决方案

来源: README.md1412-1436

函数中的 SRP

单一职责原则也适用于函数。一个函数应该只做一件事,并且做好。

违反 SRP 的函数

来源: README.md291-308

为 SRP 重构的函数

来源: README.md312-321

实践指南

如何识别类或函数何时具有多个职责

  1. 名称测试:如果您无法用一个不包含“和”或“或”的短语来描述类/函数的作用,那么它可能具有多个职责
  2. 更改测试:考虑类可能需要更改的原因。如果存在多个不相关的原因,则违反了 SRP
  3. 依赖测试:如果一个类依赖于许多不相关的组件,那么它可能承担了过多的职责

应用 SRP 的技巧

技术描述示例
类提取将复杂的类拆分成多个更简单的类UserSettings 分离为 UserAuthUserSettings
函数提取将复杂的函数分解为更小、更专注的函数emailClients 分解为 emailActiveClientsisActiveClient
组合通过组合简单、专注的组件来构建复杂的行为UserSettings 中使用 UserAuth,而不是继承
依赖注入接收依赖项,而不是创建它们将身份验证服务传递给设置管理器,而不是在内部处理身份验证

来源: README.md1392-1436 README.md291-321

与其他 SOLID 原则的联系

单一职责原则与其他 SOLID 原则协同工作

接口隔离原则(ISP)与 SRP 密切相关,因为 ISP 将单一职责概念应用于接口——使接口保持专注和最小化。

来源: README.md1378-1828

常见的陷阱和警告信号

注意这些可能表明 SRP 违反的迹象

  1. 具有许多方法和属性的大类
  2. 具有许多参数的方法
  3. 名称含糊不清的类(例如,“Manager”、“Processor”、“Handler”)
  4. 频繁更改且原因不同的类
  5. 顺序执行多个操作的方法

代码库中的示例

SRP 应用于类设计

README.md 中的示例展示了如何将身份验证与设置管理分离

// Before: Violation of SRP
class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

// After: Following SRP
class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

来源: README.md1392-1436

SRP 应用于函数设计

README 也演示了如何将 SRP 应用于函数

// Before: Violation of SRP
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

// After: Following SRP
function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

来源: README.md300-321

结论

遵守单一职责原则可以使代码更易于维护、测试和理解。通过确保每个组件只有一个职责,我们减少了复杂性,并使我们的代码库更能应对变化。请记住,SRP 并非旨在使事物尽可能小,而是为了实现关注点的正确分离和内聚。