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