JavaScript 的 5 个 SOLID 原则。如何让你的代码更 SOLID

2025-05-25

JavaScript 的 5 个 SOLID 原则。如何让你的代码更 SOLID

嗨👋!我是丹尼斯。

SOLID 原则与设计模式密切相关。了解设计模式非常重要,因为它是面试的热门话题。如果你了解它们,就能轻松理解更复杂的编程范式、架构模式和语言特性,例如响应式编程Flux 架构(Redux)JavaScript 中的生成器等等。

什么是 SOLID 原则?

SOLID代表

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

这 5 条原则将指导您如何编写更好的代码。尽管它们源自面向对象编程。我知道将 JavaScript 称为面向对象语言非常大胆 :) 无论如何,我保证,如果您理解了这些原则,那么在设计下一个解决方案时,您一定会问自己:“嘿,我是否违反了单一职责原则?”

那么,让我们开始吧

S — 单一职责原则

这可能是最简单的原则,但同时也是最容易被误解的原则。

一个模块应该只对一个参与者负责。因此,它只有一个理由去改变

例子

我们来看看下面的代码:

class TodoList {
  constructor() {
    this.items = []
  }

  addItem(text) {
    this.items.push(text)
  }

  removeItem(index) {
    this.items = items.splice(index, 1)
  }

  toString() {
    return this.items.toString()
  }

  save(filename) {
    fs.writeFileSync(filename, this.toString())
  }

  load(filename) {
    // Some implementation
  }
}

哎呀!虽然乍一看这个类没什么问题,但它违反了单一职责原则。我们给 TodoList 类添加了第二个职责,即管理数据库。

让我们修复代码,使其符合“S”原则。

class TodoList {
  constructor() {
    this.items = []
  }

  addItem(text) {
    this.items.push(text)
  }

  removeItem(index) {
    this.items = items.splice(index, 1)
  }

  toString() {
    return this.items.toString()
  }
}

class DatabaseManager {
  saveToFile(data, filename) {
    fs.writeFileSync(filename, data.toString())
  }

  loadFromFile(filename) {
    // Some implementation
  }
}

因此,我们的代码变得更具可扩展性。当然,在小型解决方案中,这一点可能不那么明显。但当应用于复杂的架构时,这一原则的意义就更加深远了。

O — 开放封闭原则

模块应该对扩展开放,但对修改关闭

这意味着如果您想扩展模块的行为,则不需要修改该模块的现有代码。

例子

class Coder {
  constructor(fullName, language, hobby, education, workplace, position) {
    this.fullName = fullName
    this.language = language
    this.hobby = hobby
    this.education = education
    this.workplace = workplace
    this.position = position
  }
}

class CoderFilter {
  filterByName(coders, fullName) {
    return coders.filter(coder => coder.fullName === fullName)
  }

  filterBySize(coders, language) {
    return coders.filter(coder => coder.language === language)
  }

  filterByHobby(coders, hobby) {
    return coders.filter(coder => coder.hobby === hobby)
  }
}

的问题CoderFilter在于,如果我们想根据其他新属性进行过滤,就必须更改CodeFilter的代码。让我们通过创建一个filterByProp函数来解决这个问题。

const filterByProp = (array, propName, value) =>
  array.filter(element => element[propName] === value)

L — 里氏替换原则

一个名称最令人困惑的原则。它是什么意思?

如果你有一个适用于基类型的函数,那么它也应该适用于派生类型

让我们来看一个经典的例子

例子

class Rectangle {
  constructor(width, height) {
    this._width = width
    this._height = height
  }

  get width() {
    return this._width
  }
  get height() {
    return this._height
  }

  set width(value) {
    this._width = value
  }
  set height(value) {
    this._height = value
  }

  getArea() {
    return this._width * this._height
  }
}

class Square extends Rectangle {
  constructor(size) {
    super(size, size)
  }
}

const square = new Square(2)
square.width = 3
console.log(square.getArea())

猜猜控制台会打印什么。如果你的答案是6,那你就答对了。当然,期望的答案是 9。这里我们可以看到里氏替换原则的典型违反。

顺便说一下,要解决这个问题,你可以Square这样定义:

class Square extends Rectangle {
  constructor(size) {
    super(size, size)
  }

  set width(value) {
    this._width = this._height = value
  }

  set height(value) {
    this._width = this._height = value
  }
}

I — 接口隔离原则

客户端不应该被迫依赖他们不使用的接口

JavaScript 中没有接口。虽然有办法模仿它们的行为,但我觉得没什么意义。我们最好把这个原则应用到 JavaScript 世界中。

例子

让我们定义一个“抽象”Phone类,它将在我们的例子中扮演接口的角色:

class Phone {
  constructor() {
    if (this.constructor.name === 'Phone')
      throw new Error('Phone class is absctract')
  }

  call(number) {}

  takePhoto() {}

  connectToWifi() {}
}

我们能用它来定义 iPhone 吗?

class IPhone extends Phone {
  call(number) {
    // Implementation
  }

  takePhoto() {
    // Implementation
  }

  connectToWifi() {
    // Implementation
  }
}

好的,但是对于旧的 Nokia 3310 来说,这个界面违反了“我”的原则

class Nokia3310 extends Phone {
  call(number) {
    // Implementation
  }

  takePhoto() {
    // Argh, I don't have a camera
  }

  connectToWifi() {
    // Argh, I don't know what wifi is
  }
}

D — 依赖倒置原则

高级模块不应该依赖于低级模块

我们来看下面的例子:

例子

class FileSystem {
  writeToFile(data) {
    // Implementation
  }
}

class ExternalDB {
  writeToDatabase(data) {
    // Implementation
  }
}

class LocalPersistance {
  push(data) {
    // Implementation
  }
}

class PersistanceManager {
  saveData(db, data) {
    if (db instanceof FileSystem) {
      db.writeToFile(data)
    }

    if (db instanceof ExternalDB) {
      db.writeToDatabase(data)
    }

    if (db instanceof LocalPersistance) {
      db.push(data)
    }
  }
}

在这种情况下,高级模块PersistanceManager依赖于低级模块,即FileSystemExternalDBLocalPersistance

为了避免这个简单案例中的问题,我们应该做这样的事情:

class FileSystem {
  save(data) {
    // Implementation
  }
}

class ExternalDB {
  save(data) {
    // Implementation
  }
}

class LocalPersistance {
  save(data) {
    // Implementation
  }
}

class PersistanceManager {
  saveData(db, data) {
    db.save(data)
  }
}

当然,这是一个过于简单的例子,但你已经明白了要点。

结论

SOLID 原则的价值并不显而易见。但如果你在设计架构时扪心自问:“我是否违反了 SOLID 原则?”,我保证你的代码质量和可扩展性会大幅提升。

非常感谢你的阅读!
欢迎在 DEV.to 和 Twitter 上关注我(@DenisVeleaev)。

和平!

文章来源:https://dev.to/denisveleaev/5-solid-principles-with-javascript-how-to-make-your-code-solid-1kl5
PREV
如何将 Github 个人资料 readme 用作作品集页面
NEXT
像专业人士一样使用 console.log()