深入淺出JavaScript前端中的設(shè)計(jì)模式
關(guān)于設(shè)計(jì)模式
軟件設(shè)計(jì)模式,又稱設(shè)計(jì)模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。它描述了在軟件設(shè)計(jì)過程中的一些不斷重復(fù)發(fā)生的問題,以及該問題的解決方案。也就是說,它是解決待定問題的一系列套路,是前輩們的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),具有一定的普遍性,可以重復(fù)使用。其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。
簡(jiǎn)單地說就是一些通用的代碼編寫方式,它是經(jīng)過不斷考驗(yàn)得出的一些總結(jié)道理,按照這樣的模式去編寫我們的代碼,沿著前人留下來的經(jīng)驗(yàn),我們就可以編寫出詩(shī)一般的代碼。
關(guān)于設(shè)計(jì)模式,我們需要知道五大基本原則(SOLID):
(1)單一職責(zé)原則:一個(gè)類,應(yīng)該僅有一個(gè)引起它變化的原因,簡(jiǎn)而言之,就是功能要單一。
(2)開放封閉原則:對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。
(3)里氏替換原則:基類出現(xiàn)的地方,子類一定出現(xiàn)。兩個(gè)字總結(jié)-繼承。
(4)接口隔離原則:一個(gè)接口應(yīng)該是一種角色,不該干的事情不敢,該干的都要干。簡(jiǎn)而言之就是降低耦合、減低依賴。
(5)依賴翻轉(zhuǎn)原則:針對(duì)接口編程,依賴抽象而不依賴具體。所編寫的對(duì)象不應(yīng)該跟具體的實(shí)例掛鉤,應(yīng)該更偏抽象的概念。
更具體地描述設(shè)計(jì)模式的好處,有以下幾點(diǎn):
①良好的封裝,不會(huì)讓內(nèi)部變量污染外部
②封裝好的代碼可以作為一個(gè)模塊給外部調(diào)用。外部無需了解細(xì)節(jié),只需按約定的規(guī)范調(diào)用。
③對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,即開放關(guān)閉原則。外部不能修改內(nèi)部代碼,保證了內(nèi)部的正確性;又留出擴(kuò)展接口,提高了靈活性。
像我們常用的各大框架,如React,Vue等都有不同設(shè)計(jì)模式的應(yīng)用,Vue中使用了觀察者模式和發(fā)布-訂閱模式。
七種常見的設(shè)計(jì)模式
設(shè)計(jì)模式一共分為3大類23種,這里主要介紹常用的幾種。
①創(chuàng)建型模式:?jiǎn)卫J?、工廠模式、建造者模式;
②結(jié)構(gòu)型模式:適配器模式、裝飾器模式、代理模式;
③行為型模式:策略模式、觀察者模式、發(fā)布訂閱模式、職責(zé)鏈模式、中介者模式。
單例模式
單例模式:一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問他的全局訪問點(diǎn),即一個(gè)類只生成一個(gè)唯一的實(shí)例。
我們?cè)谝粋€(gè)類中聲明屬性instance,當(dāng)調(diào)用函數(shù)getInstance時(shí),我們判斷instance是否已經(jīng)存在實(shí)例,若存在則訪問該instance對(duì)象,若不存在則創(chuàng)建。
class Singleton { let _instance = null; static getInstance() { if (!Singleton._instance) { Singleton.instance = new Singleton() } // 如果這個(gè)唯一的實(shí)例已經(jīng)存在,則直接返回 return Singleton._instance } } const s1 = Singleton.getInstance() const s2 = Singleton.getInstance()
Vuex就是一個(gè)典型的單例模式使用案例, store對(duì)象就是一個(gè)單例對(duì)象。
根據(jù)其功能代碼,我們可以看出單例模式的優(yōu)劣點(diǎn)都在哪里。
優(yōu)點(diǎn): 節(jié)約資源,保證訪問的一致性。
缺點(diǎn): 擴(kuò)展性不友好,因?yàn)閱卫J揭话阕孕袑?shí)例化,沒有接口。
工廠模式
這個(gè)模式我們就非常常用了,聲明一個(gè)class,然后根據(jù)傳進(jìn)來的參數(shù)去生成對(duì)應(yīng)的實(shí)例對(duì)象,就是所謂的工廠模式。每一個(gè)類就像一個(gè)已經(jīng)開設(shè)好的工廠,我們只需要告訴我們的需求,它就會(huì)生成我們想要的一個(gè)對(duì)象返回。
class Restaurant{ constructor(){ this.menuData = {}; } // 獲取菜品 getDish(dish){ if(!this.menuData[menu]){ console.log("菜品不存在,獲取失敗"); return; } return this.menuData[menu]; }, // 添加菜品 addMenu(menu,description){ if(this.menuData[menu]){ console.log("菜品已存在,請(qǐng)勿重復(fù)添加"); return; } this.menuData[menu] = menu; } // 移除菜品 removeMenu(menu){ if(!this.menuData[menu]){ console.log("菜品不存在,移除失敗"); return; } delete this.menuData[menu]; }, } class Dish{ constructor(name,description){ this.name = name; this.description = description; } eat(){ console.log(`I'm eating ${this.name},it's ${`this.description); } }
優(yōu)點(diǎn):
- 良好的封裝,訪問者無需了解創(chuàng)建過程,代碼結(jié)構(gòu)清晰。
- 擴(kuò)展性良好,通過工廠方法隔離了用戶和創(chuàng)建流程,符合開閉原則。
- 解耦了高層邏輯和底層產(chǎn)品類,符合最少知識(shí)原則,不需要的就不要去交流;
缺點(diǎn):
缺點(diǎn)就是如果我們的類定義太過抽象復(fù)雜了,會(huì)出現(xiàn)閱讀性的問題。
適配器模式
這個(gè)模式也很好理解,相當(dāng)于我們平時(shí)使用的一些產(chǎn)品,如投影儀之類的,如果我們的電線無法適配到我們的屏幕,我們就需要借助一個(gè)中間的適配器,讓兩者可以溝通起來。
interface bookDataType1 { book_id: number; status: number; create: string; update: string; } interface bookDataType2 { id: number; status: number; createTime: number; updateAt: string; } interface bookDataType3 { book_id: number; status: number; createTime: number; updateAt: number; } const getTimeStamp = function (str: string): number { //.....轉(zhuǎn)化成時(shí)間戳 return timeStamp; }; //適配器 export const bookDataAdapter = { adapterType1(list: bookDataType1[]) { const bookDataList: bookData[] = list.map((item) => { return { book_id: item.book_id, status: item.status, createAt: getTimeStamp(item.create), updateAt: getTimeStamp(item.update), }; }); return bookDataList; }, adapterType2(list: bookDataType2[]) { const bookDataList: bookData[] = list.map((item) => { return { book_id: item.id, status: item.status, createAt: item.createTime, updateAt: getTimeStamp(item.updateAt), }; }); return bookDataList; }, adapterType3(list: bookDataType3[]) { const bookDataList: bookData[] = list.map((item) => { return { book_id: item.book_id, status: item.status, createAt: item.createTime, updateAt: item.updateAt, }; }); return bookDataList; }, };
優(yōu)點(diǎn): 可以使原有邏輯得到更好的復(fù)用,有助于避免大規(guī)模改寫現(xiàn)有代碼,為了不改動(dòng)原有的代碼而做出的一種妥協(xié);
缺點(diǎn):會(huì)讓系統(tǒng)變得零亂,明明調(diào)用 A,卻被適配到了 B,如果濫用,那么對(duì)可閱讀性不太友好。簡(jiǎn)而言之搞復(fù)雜了,所以通常建議不要出現(xiàn)以上這樣的格式問題,應(yīng)該跟后端溝通好數(shù)據(jù)。
裝飾器模式
典型的大腸包小腸,當(dāng)前使用的對(duì)象無法滿足我們的全部需求,于是乎我們建一個(gè)新的類,再把這個(gè)對(duì)象在類中進(jìn)行擴(kuò)展,再生成一個(gè)新的對(duì)象。
策略模式
這個(gè)是相當(dāng)相當(dāng)常用,而且很好用的一個(gè)設(shè)計(jì)模式,可以讓我們根據(jù)不同的選擇去實(shí)現(xiàn)對(duì)應(yīng)的功能,省略了大量的if,else。
比如我們現(xiàn)在有一個(gè)需求判斷,比如我現(xiàn)在要根據(jù)別人給我的不同食材去制造料理,最暴力常規(guī)那就是if,else多寫幾個(gè)就解決了。但是這里如果我們用策略模式就可以用很清晰,很簡(jiǎn)潔的代碼去解決這個(gè)問題。
if ( 'food' == '蘋果') { 水煮() } else if ('food' == '胡蘿卜') { 炒了() } else if ('food' == '魚') { 清蒸() } else if ('food' == '豬肉') { 炸了() } else if ('food' == '牛肉') { 烤了() } else { 生吃() } // 用了策略模式,看起來舒服多了 let wayObj = { '蘋果': 水煮(), '胡蘿卜': 炒了(), '魚': 清蒸(), '豬肉': 炸了(), '牛肉': 烤了(), '不知道': 生吃() }
觀察者模式
這個(gè)模式從名字就可以看出來它是干嘛的,觀察者重點(diǎn)就是觀察,有兩個(gè)對(duì)象,一個(gè)是觀察,一個(gè)是被觀察,被觀察發(fā)生了變化,那我們觀察的對(duì)象就可以知道這個(gè)變化。
觀察者模式有一個(gè)別名叫“發(fā)布-訂閱模式”,或者說是“訂閱-發(fā)布模式”,訂閱者和訂閱目標(biāo)是聯(lián)系在一起的,當(dāng)訂閱目標(biāo)發(fā)生改變時(shí),逐個(gè)通知訂閱者。我們可以用報(bào)紙期刊的訂閱來形象的說明,當(dāng)你訂閱了一份報(bào)紙,每天都會(huì)有一份最新的報(bào)紙送到你手上,有多少人訂閱報(bào)紙,報(bào)社就會(huì)發(fā)多少份報(bào)紙,報(bào)社和訂報(bào)紙的客戶就是上面文章開頭所說的“一對(duì)多”的依賴關(guān)系。
// 觀察者模式 被觀察者Subject 觀察者Observer Subject變化 notify觀察者 let observerIds = 0; // 被觀察者Subject class Subject { constructor() { this.observers = []; } // 添加觀察者 addObserver(observer) { this.observers.push(observer); } // 移除觀察者 removeObserver(observer) { this.observers = this.observers.filter((obs) => { return obs.id !== observer.id; }); } // 通知notify觀察者 notify() { this.observers.forEach((observer) => observer.update(this)); } } // 觀察者Observer class Observer { constructor() { this.id = observerIds++; } update(subject) { // 更新 } }
發(fā)布-訂閱模式
其實(shí)上面也說了,跟觀察者模式是有異曲同工之妙的,但是它可以是一個(gè)一對(duì)多的關(guān)系,而且它需要一個(gè)中間人。
class Event { constructor() { this.eventEmitter = {}; } // 訂閱 on(type, fn) { if (!this.eventEmitter[type]) { this.eventEmitter[type] = []; } this.eventEmitter[type].push(fn); } // 取消訂閱 off(type, fn) { if (!this.eventEmitter[type]) { return; } this.eventEmitter[type] = this.eventEmitter[type].filter((event) => { return event !== fn; }); } // 發(fā)布 emit(type) { if (!this.eventEmitter[type]) { return; } this.eventEmitter[type].forEach((event) => { event(); }); } }
到此這篇關(guān)于深入淺出JavaScript前端中的設(shè)計(jì)模式的文章就介紹到這了,更多相關(guān)JS設(shè)計(jì)模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript實(shí)現(xiàn)信息增刪改查的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)信息增刪改查的方法,實(shí)例分析了javascript操作頁(yè)面元素實(shí)現(xiàn)針對(duì)頁(yè)面信息的增刪改查功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07JavaScript實(shí)現(xiàn)獲得所有兄弟節(jié)點(diǎn)的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)獲得所有兄弟節(jié)點(diǎn)的方法,實(shí)例分析了javascript節(jié)點(diǎn)遍歷的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07javascript稀疏數(shù)組(sparse array)和密集數(shù)組用法分析
這篇文章主要介紹了javascript稀疏數(shù)組(sparse array)和密集數(shù)組用法,分析javascript稀疏數(shù)組和密集數(shù)組的功能、定義與使用方法,需要的朋友可以參考下2016-12-12javascript簡(jiǎn)單性能問題及學(xué)習(xí)筆記
最近在看一本書:《高性能javaScript》,發(fā)現(xiàn)自己平時(shí)寫js存在很多小細(xì)節(jié)上的問題,雖然這些問題不會(huì)導(dǎo)致程序運(yùn)行出錯(cuò),但是會(huì)導(dǎo)致界面加載變慢,用戶體驗(yàn)變差,那么我們就來細(xì)細(xì)數(shù)一下應(yīng)該注意的地方吧2014-02-02javascript 取小數(shù)點(diǎn)后幾位幾種方法總結(jié)
這篇文章主要介紹了javascript 取小數(shù)點(diǎn)后幾位幾種方法總結(jié)的相關(guān)資料,這里提供了四種方法,幫助大家整理,需要的朋友可以參考下2017-08-08如何通過Proxy實(shí)現(xiàn)JSBridge模塊化封裝
這篇文章主要介紹了如何通過Proxy實(shí)現(xiàn)JSBridge模塊化封裝,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10