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依赖于低级模块,即FileSystem、ExternalDB和LocalPersistance。
为了避免这个简单案例中的问题,我们应该做这样的事情:
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 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com