javascript的23種設(shè)計(jì)模式示例總結(jié)大全
一、設(shè)計(jì)模式介紹
什么是設(shè)計(jì)模式
- 設(shè)計(jì)模式是解決問題的一種思想,和語言無關(guān)。在面向?qū)ο筌浖O(shè)計(jì)的工程中,針對(duì)特定的問題簡潔優(yōu)雅的一種解決方案。通俗一點(diǎn)的說,設(shè)計(jì)模式就是符合某種場(chǎng)景下某個(gè)問題的解決方案,通過設(shè)計(jì)模式可以增加代碼的可重用性,可擴(kuò)展性,可維護(hù)性,最終使得我們的代碼高內(nèi)聚、低耦合。
設(shè)計(jì)模式的五大設(shè)計(jì)原則
- 單一職責(zé):一個(gè)程序只需要做好一件事。如果功能過于復(fù)雜就拆分開,保證每個(gè)部分的獨(dú)立
- 開放封閉原則:對(duì)擴(kuò)展開放,對(duì)修改封閉。增加需求時(shí),擴(kuò)展新代碼,而不是修改源代碼。這是軟件設(shè)計(jì)的終極目標(biāo)。
- 里氏置換原則:子類能覆蓋父類,父類能出現(xiàn)的地方子類也能出現(xiàn)。
- 接口獨(dú)立原則:保持接口的單一獨(dú)立,避免出現(xiàn)“胖接口”。這點(diǎn)目前在TS中運(yùn)用到。
- 依賴導(dǎo)致原則:面向接口編程,依賴于抽象而不依賴于具體。使用方只專注接口而不用關(guān)注具體類的實(shí)現(xiàn)。俗稱“鴨子類型”
設(shè)計(jì)模式的三大類
- 創(chuàng)建型:工廠模式,抽象工廠模式,建造者模式,單例模式,原型模式
- 結(jié)構(gòu)型:適配器模式,裝飾器模式,代理模式,外觀模式,橋接模式,組合模式,享元模式
- 行為型:策略模式,模板方法模式,發(fā)布訂閱模式,迭代器模式,職責(zé)鏈模式,命令模式,備忘錄模式,狀態(tài)模式,訪問者模式,中介者模式,解釋器模式。
二、設(shè)計(jì)模式
1.工廠模式
- 工廠模式是用來創(chuàng)建對(duì)象的常見設(shè)計(jì)模式,在不暴露創(chuàng)建對(duì)象的具體邏輯,而是將邏輯進(jìn)行封裝,那么它就可以被稱為工廠。工廠模式又叫做靜態(tài)工廠模式,由一個(gè)工廠對(duì)象決定創(chuàng)建某一個(gè)類的實(shí)例。
優(yōu)點(diǎn)
- 調(diào)用者創(chuàng)建對(duì)象時(shí)只要知道其名稱即可
- 擴(kuò)展性高,如果要新增一個(gè)產(chǎn)品,直接擴(kuò)展一個(gè)工廠類即可。
- 隱藏產(chǎn)品的具體實(shí)現(xiàn),只關(guān)心產(chǎn)品的接口。
缺點(diǎn)
- 每次增加一個(gè)產(chǎn)品時(shí),都需要增加一個(gè)具體類,這無形增加了系統(tǒng)內(nèi)存的壓力和系統(tǒng)的復(fù)雜度,也增加了具體類的依賴
例子
- 一個(gè)服裝廠可以生產(chǎn)不同類型的衣服,我們通過一個(gè)工廠方法類來模擬產(chǎn)出
class DownJacket { production(){ console.log('生產(chǎn)羽絨服') } } class Underwear{ production(){ console.log('生產(chǎn)內(nèi)衣') } } class TShirt{ production(){ console.log('生產(chǎn)t恤') } } // 工廠類 class clothingFactory { constructor(){ this.downJacket = DownJacket this.underwear = Underwear this.t_shirt = TShirt } getFactory(clothingType){ const _production = new this[clothingType] return _production.production() } } const clothing = new clothingFactory() clothing.getFactory('t_shirt')// 生產(chǎn)t恤
2.抽象工廠模式
- 抽象工廠模式就是通過類的抽象使得業(yè)務(wù)適用于一個(gè)產(chǎn)品類簇的創(chuàng)建,而不負(fù)責(zé)某一個(gè)類產(chǎn)品的實(shí)例。抽象工廠可以看作普通工廠的升級(jí)版,普通工廠以生產(chǎn)實(shí)例為主,而抽象工廠的目就是生產(chǎn)工廠。
優(yōu)點(diǎn)
- 當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。
缺點(diǎn)
- 產(chǎn)品族擴(kuò)展非常困難,要增加一個(gè)系列的某一產(chǎn)品,既要在抽象的 Creator 里加代碼,又要在具體的里面加代碼。
例子
- 同樣基于上面的例子,模擬出一個(gè)抽象類,同時(shí)約束繼承子類的方法實(shí)現(xiàn)。最后再通過工廠函數(shù)返回指定的類簇
/* 抽象類 js中abstract是個(gè)保留字,實(shí)現(xiàn)抽象類只能通過new.target進(jìn)行驗(yàn)證, 防止抽象類被直接實(shí)例,另外如果子類沒有覆蓋指定方法,則拋出錯(cuò)誤 */ class ProductionFlow { constructor(){ if(new.target === ProductionFlow){ throw new Error('抽象類不能被實(shí)例') } } production(){ throw new Error('production要被重寫') } materials(){ throw new Error('materials要被重寫') } } class DownJacket extends ProductionFlow{ production(){ console.log(`材料:${this.materials()},生產(chǎn)羽絨服`) } materials(){ return '鴨毛' } } class Underwear extends ProductionFlow{ production(){ console.log(`材料:${this.materials()},生產(chǎn)內(nèi)衣`) } materials(){ return '絲光棉' } } class TShirt extends ProductionFlow{ production(){ console.log(`材料:${this.materials()},生產(chǎn)t恤`) } materials(){ return '純棉' } } function getAbstractProductionFactory(clothingType){ const clothingObj = { downJacket:DownJacket, underwear:Underwear, t_shirt:TShirt, } if(clothingObj[clothingType]){ return clothingObj[clothingType] } throw new Error(`工廠暫時(shí)不支持生產(chǎn)這個(gè)${clothingType}類型的服裝`) } const downJacketClass = getAbstractProductionFactory('downJacket') const underwearClass = getAbstractProductionFactory('underwear') const downJacket = new downJacketClass() const underwear = new underwearClass() downJacket.production() // 材料:鴨毛,生產(chǎn)羽絨服 underwear.production() // 材料:絲光棉,生產(chǎn)內(nèi)衣
3.建造者模式
- 建造者模式是一種比較復(fù)雜使用頻率較低的創(chuàng)建型設(shè)計(jì)模式,建造者模式為客戶端返回的不是一個(gè)簡單的產(chǎn)品,而是一個(gè)由多個(gè)部件組成的復(fù)雜產(chǎn)品。主要用于將一個(gè)復(fù)雜對(duì)象的構(gòu)建與他的表現(xiàn)分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
優(yōu)點(diǎn)
- 建造者獨(dú)立易擴(kuò)展
- 方便控制細(xì)節(jié)風(fēng)險(xiǎn)
缺點(diǎn)
- 產(chǎn)品必須有共同點(diǎn),范圍有限制
- 當(dāng)內(nèi)部有變化復(fù)雜時(shí),會(huì)有很多建造類
例子
下面繼續(xù)用服裝廠的生產(chǎn)流程作為例子。
// 抽象類 class Clothing { constructor() { this.clothingType = '' this.price } } class Underwear extends Clothing { constructor() { super() this.clothingType = 'underwear' this.price = 10 } } class TShirt extends Clothing { constructor() { super() this.clothingType = 't_shirt' this.price = 50 } } class DownCoat extends Clothing { constructor() { super() this.clothingType = 'DownCoat' this.price = 500 } } // 產(chǎn)品 class Purchase { constructor() { this.clothings = [] } addClothing(clothing) { this.clothings.push(clothing) } countPrice() { return this.clothings.reduce((prev, cur)=>cur.price + prev,0) } } // 廠長 class FactoryManager { createUnderwear() { throw new Error(`子類必須重寫 createUnderwear`) } createTShirt() { throw new Error(`子類必須重寫 createTShirt`) } createDownCoat() { throw new Error(`子類必須重寫 DownCoat`) } } // 工人 class Worker extends FactoryManager { constructor() { super() this.purchase = new Purchase() } createUnderwear(num) { for (let i = 0; i < num; i++) { this.purchase.addClothing(new Underwear()) } } createTShirt(num) { for (let i = 0; i < num; i++) { this.purchase.addClothing(new TShirt()) } } createDownCoat(num) { for (let i = 0; i < num; i++) { this.purchase.addClothing(new DownCoat()) } } } // 銷售 class Salesman { constructor() { this.worker = null } setWorker(worker) { this.worker = worker } reserve(clothing) { clothing.forEach((item) => { if (item.type === 'underwear') { this.worker.createUnderwear(item.num) } else if (item.type === 't_shirt') { this.worker.createTShirt(item.num) } else if (item.type === 'DownCoat') { this.worker.createDownCoat(item.num) } else { try { throw new Error('公司暫不生產(chǎn)或不存在該類型的商品') } catch (error) { console.log(error) } } }); const purchase = this.worker.purchase return purchase.countPrice() } } const salesman = new Salesman() const worker = new Worker() salesman.setWorker(worker) const order = [ { type: 'underwear', num: 10 }, { type: 't_shirt', num: 4 }, { type: 'DownCoat', num: 1 } ] console.log(`本次訂單所需金額:${salesman.reserve(order)}`)
4.單例模式
- 單例模式的思路是:保證一個(gè)類只能被實(shí)例一次,每次獲取的時(shí)候,如果該類已經(jīng)創(chuàng)建過實(shí)例則直接返回該實(shí)例,否則創(chuàng)建一個(gè)實(shí)例保存并返回。
- 單例模式的核心就是創(chuàng)建一個(gè)唯一的對(duì)象,而在javascript中創(chuàng)建一個(gè)唯一的對(duì)象太簡單了,為了獲取一個(gè)對(duì)象而去創(chuàng)建一個(gè)類有點(diǎn)多此一舉。如
const obj = {}
,obj
就是獨(dú)一無二的一個(gè)對(duì)象,在全局作用域的聲明下,可以在任何地方對(duì)它訪問,這就滿足了單例模式的條件。
優(yōu)點(diǎn)
- 內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存的開銷。
- 避免了對(duì)資源多重的占用。
缺點(diǎn)
- 違反了單一職責(zé),一個(gè)類應(yīng)該只關(guān)心內(nèi)部邏輯,而不用去關(guān)心外部的實(shí)現(xiàn)
例子
- 我們常見到的登錄彈窗,要么顯示要么隱藏,不可能同時(shí)出現(xiàn)兩個(gè)彈窗,下面我們通過一個(gè)類來模擬彈窗。
class LoginFrame { static instance = null constructor(state){ this.state = state } show(){ if(this.state === 'show'){ console.log('登錄框已顯示') return } this.state = 'show' console.log('登錄框展示成功') } hide(){ if(this.state === 'hide'){ console.log('登錄框已隱藏') return } this.state = 'hide' console.log('登錄框隱藏成功') } // 通過靜態(tài)方法獲取靜態(tài)屬性instance上是否存在實(shí)例,如果沒有創(chuàng)建一個(gè)并返回,反之直接返回已有的實(shí)例 static getInstance(state){ if(!this.instance){ this.instance = new LoginFrame(state) } return this.instance } } const p1 = LoginFrame.getInstance('show') const p2 = LoginFrame.getInstance('hide') console.log(p1 === p2) // true
5.適配器模式
- 適配器模式的目的是為了解決對(duì)象之間的接口不兼容的問題,通過適配器模式可以不更改源代碼的情況下,讓兩個(gè)原本不兼容的對(duì)象在調(diào)用時(shí)正常工作。
優(yōu)點(diǎn)
- 讓任何兩個(gè)沒有關(guān)聯(lián)的類可以同時(shí)有效運(yùn)行,并且提高了復(fù)用性、透明度、以及靈活性
缺點(diǎn)
- 過多的使用適配器模式,會(huì)讓系統(tǒng)變得零亂,不易整體把控。建議在無法重構(gòu)的情況下使用適配器。
例子
- 拿一個(gè)現(xiàn)實(shí)中的例子來說,杰克只會(huì)英語,小明只會(huì)中文,它們?cè)诮涣魃铣霈F(xiàn)了障礙,小紅同時(shí)會(huì)中英雙語,通過小紅將杰克的英語翻譯成中文,讓小明和杰克進(jìn)行無障礙的溝通,這里小紅就起到了適配器的角色。
class Jack { english() { return 'I speak English' } } class Xiaoming { chinese() { return '我只會(huì)中文' } } // 適配器 class XiaoHong { constructor(person) { this.person = person } chinese() { return `${this.person.english()} 翻譯: "我會(huì)說英語"` } } class Communication { speak(language) { console.log(language.chinese()) } } const xiaoming = new Xiaoming() const xiaoHong = new XiaoHong(new Jack()) const communication = new Communication() communication.speak(xiaoming) communication.speak(xiaoHong)
6.裝飾器模式
- 裝飾者模式能夠在不更改源代碼自身的情況下,對(duì)其進(jìn)行職責(zé)添加。相比于繼承裝飾器的做法更輕巧。通俗的講我們給心愛的手機(jī)上貼膜,帶手機(jī)殼,貼紙,這些就是對(duì)手機(jī)的裝飾。
優(yōu)點(diǎn)
- 裝飾類和被裝飾類它們之間可以相互獨(dú)立發(fā)展,不會(huì)相互耦合,裝飾器模式是繼承的一個(gè)替代模式,它可以動(dòng)態(tài)的擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能。
缺點(diǎn)
- 多層的裝飾會(huì)增加復(fù)雜度
例子
- 在編寫飛機(jī)大戰(zhàn)的游戲中,飛機(jī)對(duì)象的攻擊方式只有普通子彈攻擊,如何在不更改原代碼的情況下,為它其他的攻擊方式,如激光武器,導(dǎo)彈武器?
class Aircraft { ordinary(){ console.log('發(fā)射普通子彈') } } class AircraftDecorator { constructor(aircraft){ this.aircraft = aircraft } laser(){ console.log('發(fā)射激光') } guidedMissile(){ console.log('發(fā)射導(dǎo)彈') } ordinary(){ this.aircraft.ordinary() } } const aircraft = new Aircraft() const aircraftDecorator = new AircraftDecorator(aircraft) aircraftDecorator.ordinary() // 發(fā)射普通子彈 aircraftDecorator.laser() // 發(fā)射激光 aircraftDecorator.guidedMissile() // 發(fā)射導(dǎo)彈 // 可以看到在不更改源代碼的情況下對(duì)它進(jìn)行了裝飾擴(kuò)展
7.代理模式
- 代理模式的關(guān)鍵是,當(dāng)客戶不方便直接訪問一個(gè)對(duì)象或者不滿足需要的時(shí)候,提供一個(gè)替身對(duì)象來控制對(duì)這個(gè)對(duì)象的訪問,客戶實(shí)際上訪問的是替身對(duì)象。替身對(duì)象對(duì)請(qǐng)求做出一些處理之后,再把請(qǐng)求轉(zhuǎn)交給本體對(duì)象。
- 代理和本體接口需要一致性,代理和本體之間可以說是鴨子類型的關(guān)系,不在乎他怎么實(shí)現(xiàn)的,只要它們之間暴露的方法一致既可。
優(yōu)點(diǎn)
- 職責(zé)清晰,高擴(kuò)展性,智能化
缺點(diǎn)
- 當(dāng)對(duì)象和對(duì)象之間增加了代理可能會(huì)影響到處理的速度。
- 實(shí)現(xiàn)代理需要額外的工作,有些代理會(huì)非常的復(fù)雜。
例子
- 我們都知道,領(lǐng)導(dǎo)擁有公司的最高權(quán)限,假設(shè)公司有員工100個(gè),如果每個(gè)人都去找領(lǐng)導(dǎo)去處理事務(wù),那領(lǐng)導(dǎo)肯定會(huì)崩潰,因此領(lǐng)導(dǎo)招聘了一個(gè)秘書幫他收集整理事務(wù),秘書會(huì)在合適時(shí)間一次性將需要處理的業(yè)務(wù)交給老板處理,在這里秘書就是領(lǐng)導(dǎo)的一個(gè)代理角色。
// 員工 class Staff { constructor(affairType){ this.affairType = affairType } applyFor(target){ target.receiveApplyFor(this.affairType) } } // 秘書 class Secretary { constructor(){ this.leader = new Leader() } receiveApplyFor(affair){ this.leader.receiveApplyFor(affair) } } //領(lǐng)導(dǎo) class Leader { receiveApplyFor(affair){ console.log(`批準(zhǔn):${affair}`) } } const staff = new Staff('升職加薪') staff.applyFor(new Secretary()) // 批準(zhǔn):升職加薪
8.外觀模式
- 外觀模式本質(zhì)就是封裝交互,隱藏系統(tǒng)的復(fù)雜性,提供一個(gè)可以訪問的接口。由一個(gè)將子系統(tǒng)一組的接口集成在一起的高層接口,以提供一個(gè)一致的外觀,減少外界與多個(gè)子系統(tǒng)之間的直接交互,從而更方便的使用子系統(tǒng)。
優(yōu)點(diǎn)
- 減少系統(tǒng)的相互依賴,以及安全性和靈活性
缺點(diǎn)
- 違反開放封閉原則,有變動(dòng)的時(shí)候更改會(huì)非常麻煩,即使繼承重構(gòu)都不可行。
例子
- 外觀模式經(jīng)常被用于處理高級(jí)游覽器的和低版本游覽器的一些接口的兼容處理
function addEvent(el,type,fn){ if(el.addEventlistener){// 高級(jí)游覽器添加事件DOM API el.addEventlistener(type,fn,false) }else if(el.attachEvent){// 低版本游覽器的添加事件API el.attachEvent(`on${type}`,fn) }else {//其他 el[type] = fn } }
- 另一種場(chǎng)景,在某個(gè)函數(shù)中的某個(gè)參數(shù)可傳可不傳的情況下,通過函數(shù)重載的方式,讓傳參更靈活。
function bindEvent(el,type,selector,fn){ if(!fn){ fn = selector } // 其他代碼 console.log(el,type,fn) } bindEvent(document.body,'click','#root',()=>{}) bindEvent(document.body,'click',()=>{})
9.發(fā)布訂閱模式
- 發(fā)布訂閱又稱觀察者模式,它定義對(duì)象之間的1對(duì)N的依賴關(guān)系,當(dāng)其中一個(gè)對(duì)象發(fā)生變化時(shí),所有依賴于它的對(duì)象都會(huì)得到通知。
- 發(fā)布訂閱模式經(jīng)常出現(xiàn)在我們的工作場(chǎng)景中,如:當(dāng)你給DOM綁定一個(gè)事件就已經(jīng)使用了發(fā)布訂閱模式,通過訂閱DOM上的click事件,當(dāng)被點(diǎn)擊時(shí)會(huì)向訂閱者發(fā)布消息。
優(yōu)點(diǎn)
- 觀察者和被觀察者它們之間是抽象耦合的。并且建立了觸發(fā)機(jī)制。
缺點(diǎn)
- 當(dāng)訂閱者比較多的時(shí)候,同時(shí)通知所有的訂閱者可能會(huì)造成性能問題。
- 在訂閱者和訂閱目標(biāo)之間如果循環(huán)引用執(zhí)行,會(huì)導(dǎo)致崩潰。
- 發(fā)布訂閱模式?jīng)]有辦法提供給訂閱者所訂閱的目標(biāo)它是怎么變化的,僅僅只知道它變化了。
例子
- 比喻前段時(shí)間的冬奧會(huì),項(xiàng)目還沒有開始的時(shí)候可以提前預(yù)定,等到項(xiàng)目快開始的時(shí),APP會(huì)提前給我們發(fā)送通知即將開始的項(xiàng)目,而沒到時(shí)間的不通知,另外在項(xiàng)目還沒有開始的時(shí)候,可以取消訂閱避免接受到通知。根據(jù)這個(gè)需求我們來寫一個(gè)例子吧
class Subject { constructor(){ this.observers = {} this.key = '' } add(observer){ const key = observer.project if (!this.observers[key]) { this.observers[key] = [] } this.observers[key].push(observer) } remove(observer){ const _observers = this.observers[observer.project] console.log(_observers,11) if(_observers.length){ _observers.forEach((item,index)=>{ if(item === observer){ _observers.splice(index,1) } }) } } setObserver(subject){ this.key = subject this.notifyAllObservers() } notifyAllObservers(){ this.observers[this.key].forEach((item,index)=>{ item.update() }) } } class Observer { constructor(project,name) { this.project = project this.name = name } update() { console.log(`尊敬的:${this.name} 你預(yù)約的項(xiàng)目:【${this.project}】 馬上開始了`) } } const subject = new Subject() const xiaoming = new Observer('滑雪','xiaoming') const A = new Observer('大跳臺(tái)','A') const B = new Observer('大跳臺(tái)','B') const C = new Observer('大跳臺(tái)','C') subject.add(xiaoming) subject.add(A) subject.add(B) subject.add(C) subject.remove(B) // 取消訂閱 subject.setObserver('大跳臺(tái)') /** 執(zhí)行結(jié)果 * 尊敬的:A 你預(yù)約的項(xiàng)目:【大跳臺(tái)】 馬上開始了 * 尊敬的:C 你預(yù)約的項(xiàng)目:【大跳臺(tái)】 馬上開始了 */
10.迭代器模式
- 迭代器模式是指提供一種方法順序訪問一個(gè)聚合對(duì)象中的每個(gè)元素,并且不需要暴露該對(duì)象的內(nèi)部。
優(yōu)點(diǎn)
- 它支持以不同的方式遍歷一個(gè)聚合對(duì)象。
- 迭代器簡化了聚合類。在同一個(gè)聚合上可以有多個(gè)遍歷。
- 在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點(diǎn)
- 由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對(duì)應(yīng)增加新的迭代器類,類的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
例子
迭代器分為內(nèi)部迭代器和外部迭代器,它們有各自的適用場(chǎng)景。
- 內(nèi)部迭代器
// 內(nèi)部迭代器表示內(nèi)部已經(jīng)定義好了迭代規(guī)則,它完全接受整個(gè)迭代過程,外部只需一次初始調(diào)用。 Array.prototype.MyEach = function(fn){ for(let i = 0;i<this.length;i++){ fn(this[i],i,this) } } Array.prototype.MyEach = function(fn){ for(let i = 0;i<this.length;i++){ fn(this[i],i,this) } } [1,2,3,4].MyEach((item,index)=>{ console.log(item,index) })
- 外部迭代器
// 外部迭代器必須顯示的迭代下一個(gè)元素。它增加了調(diào)用的復(fù)雜度,但也增加了迭代器的靈活性,可以手動(dòng)控制迭代的過程。 class Iterator{ constructor(arr){ this.current = 0 this.length = arr.length this.arr = arr } next(){ return this.getCurrItem() } isDone(){ return this.current>=this.length } getCurrItem(){ return { done:this.isDone(), value:this.arr[this.current++] } } } let iterator =new Iterator([1,2,3]) while(!(item=iterator.next()).done) { console.log(item) } iterator.next() /* 下面的數(shù)據(jù)格式是不是有點(diǎn)熟悉 {done: false, value: 1} {done: false, value: 2} {done: false, value: 3} {done: true, value: undefined} */
11.狀態(tài)模式
- 允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候改變其行為,對(duì)象看起來似乎修改了它的類,通俗一點(diǎn)的將就是記錄一組狀態(tài),每個(gè)狀態(tài)對(duì)應(yīng)一個(gè)實(shí)現(xiàn),實(shí)現(xiàn)的時(shí)候根據(jù)狀態(tài)去運(yùn)行實(shí)現(xiàn)。
優(yōu)點(diǎn)
- 將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。
- 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語句塊。
- 可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。
缺點(diǎn)
- 狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(gè)數(shù)。
- 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。
- 狀態(tài)模式對(duì)"開閉原則"的支持并不太好,對(duì)切換狀態(tài)的狀態(tài)模式增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼。
例子
- lol中的瑞文的Q有三段攻擊,同一個(gè)按鍵,在不同的狀態(tài)下,攻擊的行為不同。通常情況下,我們通過if...else也可以實(shí)現(xiàn),但是這樣明顯不利于擴(kuò)展,違反了開放封閉原則。接下來用代碼來描述這種場(chǎng)景。
class State { constructor(attack){ this.attack = attack } handle(context){ console.log(this.attack) context.setState(this) } } class Context { constructor(){ this.state = null } getState(){ return this.state } setState(state){ this.state = state } } const q1 = new State('q1 第1擊'), q2 = new State('q2 第2擊'), q3 = new State('q3 第3擊'), context = new Context() q1.handle(context)//q1 第1擊 q2.handle(context)//q2 第2擊 q3.handle(context)//q3 第3擊
12.策略模式
- 策略模式指的是定義一系列算法,把他們一個(gè)個(gè)封裝起來,目的就是將算法的使用和算法的實(shí)現(xiàn)分離開來。同時(shí)它還可以用來封裝一系列的規(guī)則,比如常見的表單驗(yàn)證規(guī)則,只要這些規(guī)則指向的目標(biāo)一致,并且可以被替換使用,那么就可以用策略模式來封裝它們。
優(yōu)點(diǎn)
- 算法可以自由切換,避免了使用多層條件判斷,增加了擴(kuò)展性
缺點(diǎn)
- 策略類增多,所有策略類都需要對(duì)外暴露。
例子
- 剛?cè)脒@個(gè)行業(yè)的時(shí)候,寫表單驗(yàn)證經(jīng)常無止境的if...else寫法,意識(shí)到這種寫法不靠譜,于是我把檢驗(yàn)規(guī)則放在一個(gè)對(duì)象中,在函數(shù)中對(duì)它進(jìn)行控制,把規(guī)則與實(shí)現(xiàn)進(jìn)行了分離,每次只需要在封裝的規(guī)則中去修改配置。在后面的多種場(chǎng)景都用這種方法,解決了頻繁使用if...else的問題,當(dāng)?shù)谝淮谓佑|倒策略模式才知道這種寫法也算策略模式。
const rules = { cover_img: { must: false, msg: '請(qǐng)上傳封面圖片', val: '' }, name: { must: true, msg: '姓名不能為空', val: '' }, sex: { must: true, msg: '請(qǐng)?zhí)顚懶詣e', val: '' }, birthday: { must: false, msg: '請(qǐng)選擇生日', val: '' }, } function verify(){ for(const key in rules){ if(rules[key].must&&!rules[key].val){ console.log(rules[key].msg) } } } verify() // 姓名不能為空 // 請(qǐng)?zhí)顚懶詣e
- 上面的例子是以js方式寫的,在javascript將函數(shù)作為一等公民的語言里,策略模式就是隱形的,它已經(jīng)融入到了javascript的語言中,所以以javascript方式的策略模式會(huì)顯得簡單直接。不過我們依然要了解傳統(tǒng)的策略模式,下面來看看傳統(tǒng)的策略模式的例子。
//html----------------- <form action="http:// xxx.com/register" id="registerForm" method="post"> 請(qǐng)輸入用戶名:<input type="text" name="userName" /> 請(qǐng)輸入密碼:<input type="text" name="password" /> 請(qǐng)輸入手機(jī)號(hào)碼:<input type="text" name="phoneNumber" /> <button>提交</button> </form> // js------------------ class Strategies { constructor() { this.rules = {} } add(key, rule) { this.rules[key] = rule return this } } class Validator { constructor(strategies) { this.cache = [] // 保存檢驗(yàn)規(guī)則 this.strategies = strategies } add(dom, rules) { rules.forEach((rule) => { const strategyAry = rule.strategy.split(':') this.cache.push(() => { const strategy = strategyAry.shift() strategyAry.unshift(dom.value) strategyAry.push(rule.errorMsg) console.log(this.strategies[strategy]) return this.strategies[strategy].apply(dom, strategyAry) }) }); } start() { for (let i = 0,validatorFunc; validatorFunc =this.cache[i++]; ) { const msg = validatorFunc() if (msg) { return msg } } } } const registerForm = document.getElementById('registerForm') // 獲取formDom節(jié)點(diǎn) const strategies = new Strategies() strategies.add('isNonEmpty', function(value, errorMsg) { if (!value) { return errorMsg } }).add('minLength', function(value, length, errorMsg) { if (value.length < length) { return errorMsg } }).add('isMobile', function(value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg } }) function validataFunc() { const validator = new Validator(strategies.rules) // 多個(gè)校驗(yàn)規(guī)則 validator.add(registerForm.userName, [ { strategy: 'isNonEmpty', errorMsg: '用戶名不能為空' }, { strategy: 'minLength:10', errorMsg: '用戶名長度不能少于10位' } ]) validator.add(registerForm.password, [{ strategy: 'minLength:6', errorMsg: '密碼長度不能少于6位' }]) validator.add(registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: '手機(jī)號(hào)碼格式不對(duì)' }]) const errorMsg = validator.start() return errorMsg // 返回錯(cuò)誤信息。 } registerForm.onsubmit = function () { const errorMsg = validataFunc() if (errorMsg) { // 如果存在錯(cuò)誤信息,顯示錯(cuò)誤信息,并且阻止onsubmit默認(rèn)事件 console.log(errorMsg) return false } }
13.命令模式
- 命令模式中的命令指的是一個(gè)執(zhí)行某些特定的事情的指令。
- 命令模式最常見的應(yīng)用場(chǎng)景如:有時(shí)候需要向某些對(duì)象發(fā)送請(qǐng)求,但是并不知道請(qǐng)求的接收者是誰,也不知道被請(qǐng)求的操作是什么。此時(shí)可以通過一種松耦合的方式來設(shè)計(jì)程序,使得請(qǐng)求發(fā)送者和請(qǐng)求接收者消除彼此之間的耦合關(guān)系。
優(yōu)點(diǎn)
- 降低了代碼的耦合度,易擴(kuò)展,出現(xiàn)新的命令可以很容易的添加進(jìn)去
缺點(diǎn)
- 命令模式使用過度會(huì)導(dǎo)致代碼中存在過多的具體命令。
例子
- 假設(shè)在一個(gè)項(xiàng)目中開發(fā)某個(gè)頁面,其中某個(gè)程序員負(fù)責(zé)繪制靜態(tài)頁面,包括某些按鈕,而另一個(gè)程序員負(fù)責(zé)開發(fā)這幾個(gè)按鈕的具體行為。負(fù)責(zé)靜態(tài)頁面的程序員暫時(shí)不知道這些按鈕未來會(huì)發(fā)生什么,在不知道具體行為是什么作什么的情況下,通過命令模式的幫助,解開按鈕和負(fù)責(zé)具體行為對(duì)象之間的耦合。
// html------------------- <button id="button2">點(diǎn)擊按鈕 1</button> <button id="button2">點(diǎn)擊按鈕 2</button> <button id="button3">點(diǎn)擊按鈕 3</button> // js--------------------- const button1 = document.getElementById('button1'), button2 = document.getElementById('button2'), button3 = document.getElementById('button3'); const MenBar = { refresh:function(){ console.log('刷新菜單目錄') } } const SubMenu = { add:function(){ console.log('增加子菜單') }, del:function(){ console.log('刪除子菜單') } } function setCommand(el,command){ el.onclick = function(){ command.execute() } } class MenuBarCommand{ constructor(receiver,key){ this.receiver = receiver this.key = key } execute(){ this.receiver[this.key]() } } setCommand(button1,new MenuBarCommand(MenBar,'refresh')) setCommand(button2,new MenuBarCommand(SubMenu,'add')) setCommand(button3,new MenuBarCommand(SubMenu,'del'))
14.組合模式
- 組合模式就是由一些小的子對(duì)象構(gòu)建出的更大的對(duì)象,而這些小的子對(duì)象本身可能也是由多個(gè)孫對(duì)象組合而成的。
- 組合模式將對(duì)象組合成樹狀結(jié)構(gòu),以表示“部分-整體”的層次結(jié)構(gòu)。除了用來表示樹狀結(jié)構(gòu)之外,組合模式的另一個(gè)好處就是通過對(duì)象的多態(tài)性表現(xiàn),使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。
優(yōu)點(diǎn)
- 高層模塊調(diào)用簡單,節(jié)點(diǎn)可以自由添加
缺點(diǎn)
- 其葉對(duì)象和子對(duì)象聲明都是實(shí)現(xiàn)類,而不是接口,這違反了依賴倒置原則
例子
- 以我們最常見的文件夾和文件的關(guān)系,非常適合用組合模式來描述,文件夾可以包括子文件夾和文件,文件不能包括任何文件,這種關(guān)系讓最終會(huì)形成一棵樹。下面來實(shí)現(xiàn)文件的添加,掃描該文件里的文件,并且可以刪除文件。
// 文件夾類 class Folder { constructor(name) { this.name = name this.parent = null; this.files = [] } // 添加文件 add(file) { file.parent = this this.files.push(file) return this } // 掃描文件 scan() { console.log(`開始掃描文件夾:${this.name}`) this.files.forEach(file => { file.scan() }); } // 刪除指定文件 remove() { if (!this.parent) { return } for (let files = this.parent.files, i = files.length - 1; i >= 0; i--) { const file = files[i] if (file === this) { files.splice(i, 1) break } } } } // 文件類 class File { constructor(name) { this.name = name this.parent = null } add() { throw new Error('文件下面不能添加任何文件') } scan() { console.log(`開始掃描文件:${this.name}`) } remove() { if (!this.parent) { return } for (let files = this.parent.files, i = files.length - 1; i >= 0; i++) { const file = files[i] if (file === this) { files.splice(i, 1) } } } } const book = new Folder('電子書') const js = new Folder('js') const node = new Folder('node') const vue = new Folder('vue') const js_file1 = new File('javascript高級(jí)程序設(shè)計(jì)') const js_file2 = new File('javascript忍者秘籍') const node_file1 = new File('nodejs深入淺出') const vue_file1 = new File('vue深入淺出') const designMode = new File('javascript設(shè)計(jì)模式實(shí)戰(zhàn)') js.add(js_file1).add(js_file2) node.add(node_file1) vue.add(vue_file1) book.add(js).add(node).add(vue).add(designMode) book.remove() book.scan()
15.模塊方法模式
- 模塊方法模式是一種基于繼承的設(shè)計(jì)模式,在javascript中沒有真正意義上的繼承,所有繼承都來自原型(prototype)上的繼承,隨著ES6的class到來,實(shí)現(xiàn)了繼承的“概念”,讓我們可以以一種很方便簡潔的方式繼承,但其本質(zhì)上還是原型繼承。
- 模板方法模式由兩部分組成,第一部分是抽象父類,第二部分是具體的實(shí)現(xiàn)子類。抽象父類主要封裝了子類的算法框架,以及實(shí)現(xiàn)了一些公共的方法和其他方法的執(zhí)行順序。子類通過繼承父類,繼承了父類的算法框架,并進(jìn)行重寫。
優(yōu)點(diǎn)
- 提供公共的代碼便于維護(hù)。行為由父類控制,具體由子類來實(shí)現(xiàn)。
缺點(diǎn)
- 其每一個(gè)具體實(shí)現(xiàn)都需要繼承的子類來實(shí)現(xiàn),這無疑導(dǎo)致類的個(gè)數(shù)增加,使得系統(tǒng)龐大。
例子
- 拿咖啡和茶的例子來說,制作咖啡和茶都需要燒開水,把水煮沸是一個(gè)公共方法,隨后的怎么沖泡,把什么倒進(jìn)杯子,以及添加什么配料,它們可能各不一樣,根據(jù)以上特點(diǎn),開始我們的例子。
// 抽象父類 class Beverage { boilWater(){ console.log('把水煮沸') } brew(){ throw new Error('字類必須重寫brew方法') } pourInCup(){ throw new Error('字類必須重寫pourInCup方法') } addCondiments(){ throw new Error('字類必須重寫addCondiments方法') } init(){ this.boilWater() this.brew() this.pourInCup() this.addCondiments() } } // 咖啡類 class Coffee extends Beverage { brew(){ console.log('用沸水沖泡咖啡') } pourInCup(){ console.log('把咖啡倒進(jìn)杯子') } addCondiments(){ console.log('加糖和牛奶') } } // 茶類 class Tea extends Beverage { brew(){ console.log('用沸水侵泡茶葉') } pourInCup(){ console.log('把茶倒進(jìn)杯子') } addCondiments(){ console.log('加檸檬') } } const coffee = new Coffee() coffee.init() const tea = new Tea() tea.init()
16.享元模式
- 享元模式是一種用于性能優(yōu)化的模式,核心是運(yùn)用共享技術(shù)來有效支持大量的細(xì)粒度對(duì)象。如果系統(tǒng)中創(chuàng)建了大量的類似對(duì)象,會(huì)導(dǎo)致內(nèi)存消耗過高,通過享用模式處理重用類似對(duì)象,減少內(nèi)存消耗的問題,達(dá)到性能優(yōu)化方案。
- 享元模式的關(guān)鍵是如何區(qū)分內(nèi)部狀態(tài)和外部狀態(tài)
- 內(nèi)部狀態(tài):可以被對(duì)象共享,通常不會(huì)改變的稱為內(nèi)部狀態(tài)
- 外部狀態(tài):取決于具體的場(chǎng)景,根據(jù)具體的場(chǎng)景變化,并且不能被共享的稱為外部狀態(tài)
優(yōu)點(diǎn)
- 減少了大批量對(duì)象的創(chuàng)建,降低了系統(tǒng)了內(nèi)存。
缺點(diǎn)
- 提高了系統(tǒng)的復(fù)雜度,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固有化的性質(zhì),不應(yīng)該隨著內(nèi)部狀態(tài)的變化而變化,否則會(huì)造成系統(tǒng)的混亂。
例子
let id = 0 // 定義內(nèi)部狀態(tài) class Upload { constructor(uploadType) { this.uploadType = uploadType } // 點(diǎn)擊刪除時(shí) 小于3000直接刪除,大于3000通過confirm提示彈窗刪除。 delFile(id) { uploadManager.setExternalState(id,this) if(this.fileSize < 3000){ return this.dom.parentNode.removeChild(this.dom) } if(window.confirm(`確定要?jiǎng)h除該文件嗎?${this.fileName}`)){ return this.dom.parentNode.removeChild(this.dom) } } } // 外部狀態(tài) class uploadManager { static uploadDatabase = {} static add(id, uploadType, fileName, fileSize) { const filWeightObj = UploadFactory.create(uploadType) const dom = this.createDom(fileName, fileSize, () => { filWeightObj.delFile(id) }) this.uploadDatabase[id] = { fileName, fileSize, dom } } // 創(chuàng)建DOM 并且為button綁定刪除事件。 static createDom(fileName, fileSize, fn) { const dom = document.createElement('div') dom.innerHTML = ` <span>文件名稱:${fileName},文件大?。?{fileSize}</span> <button class="delFile">刪除</button> ` dom.querySelector('.delFile').onclick = fn document.body.append(dom) return dom } static setExternalState(id, flyWeightObj) { const uploadData = this.uploadDatabase[id] for (const key in uploadData) { if (Object.hasOwnProperty.call(uploadData, key)) { flyWeightObj[key] = uploadData[key] } } } } // 定義一個(gè)工廠創(chuàng)建upload對(duì)象,如果其內(nèi)部狀態(tài)實(shí)例對(duì)象存在直接返回,反之創(chuàng)建保存并返回。 class UploadFactory { static createFlyWeightObjs = {} static create(uploadType) { if (this.createFlyWeightObjs[uploadType]) { return this.createFlyWeightObjs[uploadType] } return this.createFlyWeightObjs[uploadType] = new Upload(uploadType) } } // 開始加載 const startUpload = (uploadType, files)=>{ for (let i = 0, file; file = files[i++];) { uploadManager.add(++id, uploadType, file.fileName, file.fileSize) } } startUpload('plugin', [ {fileName: '1.txt',fileSize: 1000}, {fileName: '2.html',fileSize: 3000}, {fileName: '3.txt',fileSize: 5000} ]); startUpload('flash', [ {fileName: '4.txt',fileSize: 1000}, {fileName: '5.html',fileSize: 3000}, {fileName: '6.txt',fileSize: 5000} ]);
17.職責(zé)鏈模式
- 職責(zé)鏈模式的定義是:使用多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對(duì)象鏈成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,知道有一個(gè)對(duì)象處理它為止。
優(yōu)點(diǎn)
- 降低耦合度,它將請(qǐng)求的發(fā)送者和接收者解耦。
- 簡化了對(duì)象,使得對(duì)象不需要知道鏈的結(jié)構(gòu)。
- 增強(qiáng)給對(duì)象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序,允許動(dòng)態(tài)地新增或者刪除責(zé)任。
缺點(diǎn)
- 不能保證每一條請(qǐng)求都一定被接收。
- 系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便,可能會(huì)造成循環(huán)調(diào)用。
- 可能不容易觀察運(yùn)行時(shí)的特征,有礙于排除問題。
例子
- 假設(shè)我們負(fù)責(zé)一個(gè)手機(jī)售賣的電商網(wǎng)站,分別繳納500元和200元定金的兩輪預(yù)訂后,會(huì)分別收到100元和50元的優(yōu)惠券,而沒有支付定金的則視為普通購買,沒有優(yōu)惠券,并且在庫存有限的情況下也無法保證能購買到。
class Order500 { constructor(){ this.orderType = 1 } handle(orderType, pay, stock){ if(orderType === this.orderType&&pay){ console.log('500元定金預(yù)約,得到100元優(yōu)惠券') }else { return 'nextSuccessor' } } } class Order200 { constructor(){ this.orderType = 2 } handle(orderType, pay, stock){ if(orderType === this.orderType&&pay){ console.log('200元訂金預(yù)約,得到50元優(yōu)惠卷') }else { return 'nextSuccessor' } } } class OrderNormal { constructor(){ this.stock = 0 } handle(orderType, pay, stock){ if (stock > this.stock) { console.log('普通購買,無優(yōu)惠卷') } else { console.log('手機(jī)庫存不足') } } } class Chain { constructor(order){ this.order = order this.successor = null } setNextSuccessor(successor){ return this.successor = successor } passRequest(...val){ const ret = this.order.handle.apply(this.order,val) if(ret === 'nextSuccessor'){ return this.successor&&this.successor.passRequest.apply(this.successor,val) } return ret } } console.log(new Order500()) var chainOrder500 = new Chain( new Order500() ); var chainOrder200 = new Chain( new Order200() ); var chainOrderNormal = new Chain( new OrderNormal() ); chainOrder500.setNextSuccessor( chainOrder200 ); chainOrder200.setNextSuccessor( chainOrderNormal ); chainOrder500.passRequest( 1, true, 500 ); // 輸出:500 元定金預(yù)購,得到 100 優(yōu)惠券 chainOrder500.passRequest( 2, true, 500 ); // 輸出:200 元定金預(yù)購,得到 50 優(yōu)惠券 chainOrder500.passRequest( 3, true, 500 ); // 輸出:普通購買,無優(yōu)惠券 chainOrder500.passRequest( 1, false, 0 ); // 輸出:手機(jī)庫存不足
18.中介模式
- 中介者模式的作用就是解除對(duì)象與對(duì)象之間的緊密耦合關(guān)系。增加一個(gè)中介者對(duì)象之后,所有相關(guān)對(duì)象都通過中介者對(duì)象來通信,而不是相互引用,所以當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),只需要通過中介者對(duì)象即可。中介者使各對(duì)象之間耦合松散,而且可以獨(dú)立改變他們之間的交互。中介者模式使網(wǎng)狀的多對(duì)多關(guān)系變成了相對(duì)簡單的一對(duì)多關(guān)系。
優(yōu)點(diǎn)
- 降低了類的復(fù)雜度,將一對(duì)多轉(zhuǎn)化成了一對(duì)一。各個(gè)類之間的解耦。
缺點(diǎn)
- 當(dāng)中介者變得龐大復(fù)雜,導(dǎo)致難以維護(hù)。
例子
// html----------- 選擇顏色:<select name="" id="colorSelect"> <option value="">請(qǐng)選擇</option> <option value="red">紅色</option> <option value="blue">藍(lán)色</option> </select> <br /> 選擇內(nèi)存:<select name="" id="memorySelect"> <option value="">請(qǐng)選擇</option> <option value="32G">32G</option> <option value="63G">64G</option> </select> <br /> 輸入購買數(shù)量:<input type="text" id="numberInput" /> <br /> <div>你選擇了顏色:<span id="colorInfo"></span></div> <div>你選擇了內(nèi)存:<span id="memoryInfo"></span></div> <div>你選擇了數(shù)量:<span id="numberInfo"></span></div> <button id="nextBtn" disabled="true">請(qǐng)選擇手機(jī)顏色和購買數(shù)量</button> // js ------------------- const goods = { "red|32G": 3, "red|16G": 0, "blue|32G": 1, "blue|16G": 6 }, colorSelect = document.getElementById('colorSelect'), memorySelect = document.getElementById('memorySelect'), numberInput = document.getElementById('numberInput'), colorInfo = document.getElementById('colorInfo'), memoryInfo = document.getElementById('memoryInfo'), numberInfo = document.getElementById('numberInfo'), nextBtn = document.getElementById('nextBtn'), mediator = (function () { return { changed(obj) { const color = colorSelect.value, memory = memorySelect.value, number = numberInput.value, stock = goods[`${color}|${memory}`] if (obj === colorSelect) { colorInfo.innerHTML = color } else if (obj === memorySelect) { memoryInfo.innerHTML = memory } else if (obj === numberInput) { numberInfo.innerHTML = number } if (!color) { nextBtn.disabled = true nextBtn.innerHTML = '請(qǐng)選擇手機(jī)顏色' return } if (!memory) { nextBtn.disabled = true nextBtn.innerHTML = '請(qǐng)選擇內(nèi)存大小' return } if (Number.isInteger(number - 0) && number < 1) { nextBtn.disabled = true nextBtn.innerHTML = '請(qǐng)輸入正確的購買數(shù)量' return } nextBtn.disabled = false nextBtn.innerHTML = '放入購物車' } } })() colorSelect.onchange = function () { mediator.changed(this) } memorySelect.onchange = function () { mediator.changed(this) } numberInput.oninput = function () { mediator.changed(this) }
19.原型模式
- 原型模式是指原型實(shí)例指向創(chuàng)建對(duì)象的種類,通過拷貝這些原型來創(chuàng)建新的對(duì)象,說白了就是克隆自己,生成一個(gè)新的對(duì)象。
優(yōu)點(diǎn)
- 不再依賴構(gòu)造函數(shù)或者類創(chuàng)建對(duì)象,可以將這個(gè)對(duì)象作為一個(gè)模板生成更多的新對(duì)象。
缺點(diǎn)
- 對(duì)于包含引用類型值的屬性來說,所有實(shí)例在默認(rèn)的情況下都會(huì)取得相同的屬性值。
例子
const user = { name:'小明', age:'30', getInfo(){ console.log(`姓名:${this.name},年齡:${this.age}`) } } const xiaozhang = Object.create(user) xiaozhang.name = '小張' xiaozhang.age = 18 xiaozhang.getInfo() // 姓名:小張,年齡:18 user.getInfo() // 姓名:小明,年齡:30
20.備忘錄模式
- 備忘錄模式就是在不破壞封裝的前提下,捕獲一個(gè)對(duì)象內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),以保證以后可以將對(duì)象恢復(fù)到原先的狀態(tài)。
優(yōu)點(diǎn)
- 給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶能夠比較方便地回到某個(gè)歷史的狀態(tài)。
- 實(shí)現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細(xì)節(jié)。
缺點(diǎn)
- 如果類的成員變量過多,勢(shì)必會(huì)占用比較大的資源,而且每一次保存都會(huì)消耗一定的內(nèi)存。
例子
// 棋子 class ChessPieces { constructor(){ this.chess = {} } // 獲取棋子 getChess(){ return this.chess } } // 記錄棋路 class Record { constructor(){ this.chessTallyBook = [] // 記錄棋路 } recordTallyBook(chess){ // console.log(this.chessTallyBook.includes(chess)) const isLoadtion = this.chessTallyBook.some( item=>item.location === chess.location ) if(isLoadtion){ console.log(`${chess.type},${chess.location}已存在其他棋子`) }else { this.chessTallyBook.push(chess) } // this.chessTallyBook.some(item=>item.location === chess.location) } getTallyBook(){ return this.chessTallyBook.pop() } } // 下棋規(guī)則 class ChessRule { constructor(){ this.chessInfo = {} } playChess(chess){ this.chessInfo = chess } getChess(){ return this.chessInfo } // 記錄棋路 recordTallyBook(){ return new ChessPieces(this.chessInfo) } // 悔棋 repentanceChess(chess){ this.chessInfo = chess.getTallyBook() } } const chessRule = new ChessRule() const record = new Record() chessRule.playChess({ type:'黑棋', location:'X10,Y10' }) record.recordTallyBook(chessRule.getChess())//記錄棋路 chessRule.playChess({ type:'白棋', location:'X11,Y10' }) record.recordTallyBook(chessRule.getChess())//記錄棋路 chessRule.playChess({ type:'黑棋', location:'X11,Y11' }) record.recordTallyBook(chessRule.getChess())//記錄棋路 chessRule.playChess({ type:'白棋', location:'X12,Y10' }) console.log(chessRule.getChess())//{type:'白棋',location:'X12,Y10'} chessRule.repentanceChess(record) // 悔棋 console.log(chessRule.getChess())//{type:'黑棋',location:'X11,Y11'} chessRule.repentanceChess(record) // 悔棋 console.log(chessRule.getChess())//{type:'白棋',location:'X11,Y10'}
21.橋接模式
- 橋接模式是指將抽象部分與它的實(shí)現(xiàn)部分分離,使它們各自獨(dú)立的變化,通過使用組合關(guān)系代替繼承關(guān)系,降低抽象和實(shí)現(xiàn)兩個(gè)可變維度的耦合度。
優(yōu)點(diǎn)
- 抽象和實(shí)現(xiàn)的分離。優(yōu)秀的擴(kuò)展能力。實(shí)現(xiàn)細(xì)節(jié)對(duì)客戶透明。
缺點(diǎn)
- 橋接模式的引入會(huì)增加系統(tǒng)的理解與設(shè)計(jì)難度,由于聚合關(guān)聯(lián)關(guān)系建立在抽象層,要求開發(fā)者針對(duì)抽象進(jìn)行設(shè)計(jì)與編程。
例子
- 比如我們所用的手機(jī),蘋果的iphoneX,和華為的mate40,品牌和型號(hào)就是它們共同的抽象部分,可以把他們單獨(dú)提取出來。
class Phone { constructor(brand,modle){ this.brand = brand this.modle = modle } showPhone(){ return `手機(jī)的品牌:${this.brand.getBrand()},型號(hào)${this.modle.getModle()}` } } class Brand { constructor(brandName){ this.brandName = brandName } getBrand(){ return this.brandName } } class Modle { constructor(modleName){ this.modleName = modleName } getModle(){ return this.modleName } } const phone = new Phone(new Brand('華為'),new Modle('mate 40')) console.log(phone.showPhone())
22.訪問者模式
- 訪問者模式是將數(shù)據(jù)的操作和數(shù)據(jù)的結(jié)構(gòu)進(jìn)行分離,對(duì)數(shù)據(jù)中各元素的操作封裝獨(dú)立的類,使其在不改變數(shù)據(jù)結(jié)構(gòu)情況下擴(kuò)展新的數(shù)據(jù)。
優(yōu)點(diǎn)
- 符合單一職責(zé)原則。具有優(yōu)秀的擴(kuò)展性和靈活性。
缺點(diǎn)
- 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
例子
class Phone { accept() { throw new Error('子類的accept必須被重寫') } } class Mata40Pro extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return phoneVisitor.visit(this) } } class IPhone13 extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return phoneVisitor.visit(this) } } // 訪問者類 class PhoneVisitor { visit(phone) { if (phone.constructor === IPhone13) { return { os: 'ios', chip: 'A15仿生芯片', screen: '電容屏' } } else if (phone.constructor === Mata40Pro) { return { os: 'HarmonyOS', chip: 'Kirin 9000', GPUType: 'Mali-G78', port: 'type-c' } } } } const mata40Pro = new Mata40Pro() console.log(mata40Pro.accept())
23.解釋器模式
- 解釋器模式提供了評(píng)估語言的語法或表達(dá)式的方式,它屬于行為型模式。這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口該接口,該接口解釋一個(gè)特定的上下文。
優(yōu)點(diǎn)
- 可擴(kuò)展性比較好,靈活。增加了新的解釋表達(dá)式的方式。
缺點(diǎn)
- 可利用場(chǎng)景比較少,在web開發(fā)中幾乎不可見。對(duì)于復(fù)雜的環(huán)境比較難維護(hù)。
- 解釋器模式會(huì)引起類膨脹。它還采用遞歸調(diào)用方法,沒控制好可能會(huì)導(dǎo)致崩潰。
例子
class TerminalExpression { constructor(data) { this.data = data } interpret(context) { if (context.indexOf(this.data) > -1) { return true; } return false; } } class OrExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.expr1.interpret(context) || this.expr2.interpret(context); } } class AndExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.expr1.interpret(context) && this.expr2.interpret(context); } } class InterpreterPatternDemo { static getMaleExpression() { const robert = new TerminalExpression("小明"); const john = new TerminalExpression("小龍"); return new OrExpression(robert, john); } static getMarriedWomanExpression() { const julie = new TerminalExpression("張三"); const married = new TerminalExpression("小紅"); return new AndExpression(julie, married); } static init(args) { const isMale = this.getMaleExpression(); const isMarriedWoman = this.getMarriedWomanExpression(); console.log(`小龍是男性?${isMale.interpret("小龍")}`) console.log(`小紅是一個(gè)已婚婦女?${isMarriedWoman.interpret("小紅 張三")}`) } } InterpreterPatternDemo.init()
總結(jié)
- 以上是我歷時(shí)將近一個(gè)月的學(xué)習(xí)總結(jié),然而一個(gè)月的時(shí)間是遠(yuǎn)遠(yuǎn)不夠的,在寫完這篇文章后,依舊對(duì)某些設(shè)計(jì)模式的應(yīng)用場(chǎng)景缺乏了解。設(shè)計(jì)模式是要長時(shí)間深入研究的知識(shí)點(diǎn),需要結(jié)合實(shí)際的場(chǎng)景去練習(xí)模仿,不斷的去思考。另外由于js的特性,很多設(shè)計(jì)模式在js中是殘缺的,非完全體的,強(qiáng)行用js去模仿傳統(tǒng)的設(shè)計(jì)模式顯的有點(diǎn)雞肋。但是隨著typescript的出現(xiàn),設(shè)計(jì)模式在ts中可以無限接近傳統(tǒng)的設(shè)計(jì)模式,后續(xù)計(jì)劃寫一篇ts版本的設(shè)計(jì)模式博客。
- 本片文章學(xué)習(xí)來源:
- 書籍《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》
- 雙越老師的:《Javascript 設(shè)計(jì)模式系統(tǒng)講解與應(yīng)用》
- 以及百度搜索借鑒的各種案例
以上就是javascript的23種設(shè)計(jì)模式總結(jié)大全的詳細(xì)內(nèi)容,更多關(guān)于javascript設(shè)計(jì)模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript中Date對(duì)象應(yīng)用之簡易日歷實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了javascript中Date對(duì)象應(yīng)用之簡易日歷實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07教你JS更簡單的獲取表單中數(shù)據(jù)(formdata)
這篇文章主要介紹了JS更簡單的獲取表單中數(shù)據(jù)(formdata),本文給大家分享的js獲取表單數(shù)據(jù)更簡潔,通過兩種方法結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05詳解javaScript中Number數(shù)字類型的使用
Number和Math都屬于JavaScript中的內(nèi)置對(duì)象,Number數(shù)字類型作為基礎(chǔ)數(shù)據(jù)類型,我們?cè)陂_發(fā)過程中會(huì)經(jīng)常用到,包括數(shù)字精度的格式化,還有字符串轉(zhuǎn)換成數(shù)字等操作。本文將詳細(xì)講解其用法,感興趣的可以了解一下2022-04-04JavaScript偽數(shù)組和數(shù)組的使用與區(qū)別
這篇文章主要給大家介紹了關(guān)于JavaScript偽數(shù)組和數(shù)組使用與區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05javascript 閉包詳解及簡單實(shí)例應(yīng)用
這篇文章主要介紹了javascript 閉包詳解及應(yīng)用的相關(guān)資料,需要的朋友可以參考下2016-12-12js實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果,這種效果大家經(jīng)常遇到,示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-10-10