JavaScript 中的 SOLID 原则(五):“D”代表什么

本篇是SOLID原则的最后一篇,建议先阅读前四部分:

JavaScript 中的 SOLID 原则(一):“S”代表什么

JavaScript 中的 SOLID 原则(二):“O”代表什么

JavaScript 中的 SOLID 原则(三):“L”代表什么

JavaScript 中的 SOLID 原则(四):“I”代表什么

这是SOLID的第五篇文章(原文一共五篇),作者是serhiirubets,感谢持续关注。

依赖倒置原则

D - 依赖倒置原则 这个原则是指:高级模块不应该依赖低级模块;两者都应该依赖于抽象,抽象应该不依赖于细节,细节应该取决于抽象。

举个例子,假设我们想处理movie数据,我们创建了一个简单的Movie类:

class Movie {
  constructor(title,description) {}
}

我们还需要保存视频信息到localStorage,为了遵循单一职责原则,我们单独创建一个类:

class MovieStorage {
  setItem(data) {}
  getItemById(id) {}
  getAll() {}
}

一切都很好,而且我们的逻辑会在其他地方使用。

const movieStorage = new MovieStorage()
const ironMan = new MovieStorage('Iron man', 'Movie about Iron man')
const spiderMan = new MovieStorage('Spider man', 'Movie about Spider man')
movieStorage.setItem(ironMan)
movieStorage.setItem(spiderMan)
// here could be different other logic
movieStorage.getItemById(1);

然后我们还想保存数据到本地文件系统,没问题,再创建一个类:

class MovieFileStorage {
  save(data){}
  editFile(data){}
  readMovieById(id){}
  readAllMovies(){}
}

现在我们需要把之前使用localStorage的地方,替换成fileStorage

Snipaste_2022-04-11_17-45-14.jpg

看起来没有什么问题,我们只是删除并替换了4行代码,但就像我们之前讨论的,如果你在很多文件中多次使用了local Storage,很难找到所有使用的地方并正确的修改它们。而且如果你已经为此写了测试,所有测试也需要进行修改。

修改后的代码可以正常工作,但是随着时间的推移,对本地文件系统占用越来越大,我们打算切换到数据库进行存储,MongoDB或SQL,我们应该怎么做?遵循“单一职责原则”,我们创建了一个DB存储类:

class MovieDBStorage{
  insert(data){}
  update(data){}
  selectAll(){}
  selectById(id){}
}

现在我们遇到了同样的问题,我们需要查找所有的文件,把文件系统的逻辑修改为数据库操作,需要查找到所有相关文件中的调用并修改方法名,签名,为此编写的测试也需要进行调整。

Snipaste_2022-04-11_17-45-40.jpg

使用的地方越多,修改起来越困难,这也是导致代码出现bug的原因之一。

希望你可以理解这个问题,我们来看看怎么才能避免它,还记得依赖倒置原则吗:高级模块不应该依赖低级模块;两者都应该依赖于抽象,抽象不应该依赖细节,细节应该取决于抽象。

我们从抽象开始重构吧:我们创建一个MoveStorage类,这个类将会是我们的“抽象”。抽象不应该依赖细节,我们应该怎么实现它呢?很简单,我们为MoveStorage类创建方法,这些方法用来代替MoveDBStorage,MovieFileStorage。

class MovieStorage {
  save(data) {}
  edit(data) {}
  getById(id) {}
  getAll() {}
}

这个类就像一个接口,我们所有的代码都会使用这些方法,它们的名称不会被改变,也就是说我们的高级模块(我们使用“抽象”的地方)将不依赖于我们的内部逻辑。

接下来,我们为每个存储方式创建特定的类,而且每个类使用的方法名、参数都和我们的“抽象”类保持一致:

class MovieFileStorage {
  save(data) {}
  edit(data) {}
  getById(id) {}
  getAll() {}
}
class MovieDBStorage {
  save(data) {}
  edit(data) {}
  getById(id) {}
  getAll() {}
}

最后我们来调整我们的“抽象”:

class MovieStorage {
    constructor (storage) {
        this.storage = storage;
    }
    save(data) {
        this.storage.save(data)
    }
    edit(data) {
        this.storage.edit(data)
    }
    getById(id) {
        this.storage.getById(id)
    }
    getAll() {
        this.storage.getAll()
    }
}

现在我们的“抽象”已经不依赖细节了,MovieStorage接收任何存储的实例,并且实例遵循我们的接口:

const movieStorage = new MovieStorage(new MovieFileStorage())
movieStorage.save(ironMan)
movieStorage.save(spiderMan)
moveStorage.getById(1)

如果我们想把文件存储修改为缓存存储、本地/会话存储、MongoDB、SQL等,我们只需要准备对应的存储类(用于mongo、redis、sql),它应该实现和我们的“抽象”同名的方法,并把新的类实例传递到构造器中:

const movieStorage = new MovieStorage(new MovieDBStorage())
movieStorage.save(ironMan)
movieStorage.save(spiderMan)
movieStorage.getById(1)

我们只是改变了传递的参数:MovieStorage接收的实例从new MovieFileStorage()修改成了new MovieDBStorage()。我们不需要查找并修改所有的文件,也不需要修改已有的测试。我们所有的文件都使用了相同的抽象,而且我们的抽象不依赖于逻辑,抽象即逻辑。

这就是JS中“SOLID”的收尾,希望你可以在时间中至少使用到他们中的一个。你可以全部使用,也可以只选择一个,比如:单一职责原则,查看你的代码是否都遵循了这个原则,如果没有,那就重构你的代码吧。

你也可以使用“依赖倒置原则”,并检查你的代码是否符合这个原则,幸运的是,像“Angular”或“NestJS”这些框架遵循了这个原则,你可以在使用他们的项目中看到具体的实践。

我们来做个回顾吧:

1、单一职责原则(SRP):一个类应该有且只有一个职责,解决一项特定任务。

2、开放封闭原则(OCP):一个类应该对扩展开放,对修改关闭。一个类在应用的其他地方已经开始使用,就不应该再修改它。

3、里氏替换原则(LSP):派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,都可以被子类替换。值得注意的是,当通过继承实现多态行为时,如果派生类没有遵守LSP,可能会让系统引发异常。

4、接口隔离原则(ISP):基类不应该包含他们子类不使用的方法,也就是说一个接口应该拥有尽可能少的行为。应该把那些大而全的接口拆分成一些小的、具体的接口,这样客户端就只需关心他们要用到的接口。

5、依赖倒置原则(DIP):高级模块不应该依赖低级模块,相反,他们应该依赖抽象类或者接口。也就是不应该在高级模块中使用具体的低级模块,应该遵从依赖于抽象(接口)而不是一个实例(类)。

感谢阅读!欢迎关注微信公众号”混沌前端“。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容