JS設(shè)計(jì)模式之命令模式的用法詳解
相關(guān)定義:
使用命令模式,可以將【請(qǐng)求的調(diào)用者】和【請(qǐng)求的執(zhí)行者】解耦。
調(diào)用者通過【持有命令對(duì)象】來【間接調(diào)用】接收者的方法,而無需【直接引用】接收者或了解其【具體實(shí)現(xiàn)】。
這種解耦使得我們能夠更加靈活地【擴(kuò)展】和【改變】命令的調(diào)用方式。
例如,我們可以【將命令對(duì)象保存在【隊(duì)列中】】,實(shí)現(xiàn)命令的【排隊(duì)】和【異步執(zhí)行】。
還可以記錄命令的【歷史,以支持撤銷和重做】操作。
命令模式應(yīng)用場景,【菜單操作】、【多級(jí)撤銷】、【批處理任務(wù)】等。
自我理解:
執(zhí)行者對(duì)象E:普通對(duì)象,提供了整個(gè)流程中的執(zhí)行方法,此方法對(duì)于具體的命令是可感知的,對(duì)于調(diào)用者是無感知的!
抽象命令類A:規(guī)定了具體命令的基本結(jié)構(gòu),為調(diào)用者提供統(tǒng)一的調(diào)用接口。
具體命令類S:是對(duì)抽象命令類的實(shí)現(xiàn),核心是封裝了執(zhí)行者,準(zhǔn)確來說命令只封裝了執(zhí)行者的部分方法;抽象方法的執(zhí)行本質(zhì)上是執(zhí)行了這些方法。
調(diào)用者對(duì)象C:封裝了兩個(gè)方法和一個(gè)屬性;屬性表示的是當(dāng)前命令,第一個(gè)方法是設(shè)置/切換命令具體值,第二個(gè)方法是執(zhí)行此方法。
S封裝了E,C只能接觸到S,所以C和E是解耦的
A和S的關(guān)系是一對(duì)多
解耦體現(xiàn):
C無需了解S具體內(nèi)容,只需完成觸發(fā) ;
S無需知道E怎么完成任務(wù),只需調(diào)用E暴露出來的方法即可;
S和E之間是多對(duì)多的關(guān)系,即一個(gè)S可以由多個(gè)E的部分方法組合完成,一個(gè)E也可以被不同的S封裝其上不同的部分方法;
C維護(hù)的不只是一個(gè)S,還可以是一個(gè)S隊(duì)列,當(dāng)觸發(fā)到來的時(shí)候,S隊(duì)列依次執(zhí)行。
代碼舉例1:
// 接收者對(duì)象 class Light { ? ? turnOn() {} ? ? turnOff() {} } // 命令接口 abstract class Command { ? ? abstract execute():void; } // 具體命令:打開燈 class TurnOnCommand extends Command { ? ? constructor(public light: Light) { super() } ? ? execute() { this.light.turnOn() } } // 具體命令:關(guān)閉燈 class TurnOffCommand extends Command { ? ? constructor(public light: Light) { super() } ? ? execute() { this.light.turnOff() } } // 調(diào)用者對(duì)象 class RemoteControl { ? ? command: Command; ? ? setCommand(command: Command) { this.command = command } ? ? pressButton() { this.command.execute() } } // 使用示例 // 創(chuàng)建執(zhí)行者 const light = new Light(); // 創(chuàng)建具體命令對(duì)象,封裝執(zhí)行者 const turnOnCommand = new TurnOnCommand(light); const turnOffCommand = new TurnOffCommand(light); // 創(chuàng)建調(diào)用者對(duì)象 const remoteControl = new RemoteControl(); // 設(shè)置具體命令對(duì)象然后執(zhí)行 remoteControl.setCommand(turnOnCommand); remoteControl.pressButton();
代碼舉例2:
使用命令設(shè)計(jì)模式可以靈活組合網(wǎng)絡(luò)請(qǐng)求,如下所示:
// 請(qǐng)求接收者 class Reciever { ? async get(path: string) { ? ? const res = await fetch(`https://rec.example.com/${path}`); ? ? const data = await res.json(); ? } } // 命令接口 class Cmd { exe() {} } // 具體命令:發(fā)送請(qǐng)求 class RCd extends Cmd { ? constructor(public rec, public url) { ? ? super(); ? } ? exe() { ? ? return this.rec.get(this.url); ? } } // 調(diào)用者對(duì)象 class RM { ? cQueue: Cmd[] = []; ? add(command: Cmd) { ? ? this.cQueue.push(command); ? } ? pReq() { ? ? const promises = this.cQueue.map((command) => command.exe()); ? ? return Promise.all(promises); ? } } // 使用示例 const rec = new Reciever(); // 創(chuàng)建請(qǐng)求接收者 const rM = new RM(); // 創(chuàng)建請(qǐng)求管理者 // 添加具體請(qǐng)求命令 rM.add(new RCd(rec, 'data1')); rM.add(new RCd(rec, 'data2')); rM.add(new RCd(rec, 'data3')); rM.pReq() ? .then(() => { ? ? console.log('所有請(qǐng)求已完成'); ? }) ? .catch((error) => { ? ? console.error('請(qǐng)求出錯(cuò):', error); ? });
代碼舉例3:
使用命令設(shè)計(jì)模式實(shí)現(xiàn)撤銷和重做,用到了棧數(shù)據(jù)結(jié)構(gòu),如下所示:
// 命令接口:想要用命令策略實(shí)現(xiàn)撤銷、重做就必須先在抽象接口中定義好撤銷的接口 abstract class Cmd { ? abstract exe(): void; ? abstract undo(): void; } // 具體命令類 - 加法命令 class AddCmd extends Cmd { ? constructor(public rec: Rec, public value: number) { ? ? super(); ? } ? exe() { ? ? this.rec.add(this.value); ? } ? undo() { ? ? this.rec.subtract(this.value); ? } } // 接收者類 class Rec { ? result = 0; ? add(value: number) { this.result += value } ? subtract(value: number) { this.result -= value } } // 調(diào)用者/發(fā)送者 class Invoker { ? cmds: Cmd[] = []; ? xcmd: Cmd[] = []; ? exe(cmd: Cmd) { ? ? cmd.exe(); ? ? this.cmds.push(cmd); ? } ? // 重點(diǎn) ? undo() { ? ? const cmd = this.cmds.pop(); ? ? if (!cmd) return; ? ? cmd.undo(); ? ? this.xcmd.push(cmd); ? } ? // 重點(diǎn) ? redo() { ? ? const cmd = this.xcmd.pop(); ? ? if (cmd) { ? ? ? cmd.exe(); ? ? ? this.cmds.push(cmd); ? ? } ? } } // 示例用法 const rec = new Rec(); // 創(chuàng)建接收者對(duì)象 const ivk = new Invoker(); // 創(chuàng)建調(diào)用者對(duì)象 const addCmd = new AddCmd(rec, 5); // 創(chuàng)建加法命令 ivk.exe(addCmd); // 執(zhí)行加法命令,結(jié)果為:5 ivk.undo(); // rec.result = 0 ivk.redo(); // rec.result = 5
命令設(shè)計(jì)模式和策略設(shè)計(jì)模式的不同:
命令設(shè)計(jì)模式的最小操作單元是【命令對(duì)象】;而策略設(shè)計(jì)模式的最小操作單元是方法,或者算法。
命令設(shè)計(jì)模式一次只操作一個(gè)命令對(duì)象;而策略設(shè)計(jì)模式為了完成任務(wù)可以組合多個(gè)策略。
命令設(shè)計(jì)模式一般不會(huì)將某個(gè)命令單獨(dú)保存到內(nèi)部狀態(tài)中;而策略設(shè)計(jì)模式必須保存當(dāng)前的策略。
使用命令設(shè)計(jì)模式可以實(shí)現(xiàn)撤銷、重做等功能、這反映出各個(gè)命令之間是平等關(guān)系;而策略設(shè)計(jì)模式的各個(gè)策略之間可能是先后順序關(guān)系。
原生使用
下面這些是 JavaScript 中常見的原生部分,它們?cè)谀撤N程度上使用到了命令模式的思想和機(jī)制。通過封裝行為成具體的對(duì)象并在需要時(shí)進(jìn)行調(diào)用,這些原生功能可以提供更靈活、可擴(kuò)展的方式來處理相關(guān)的請(qǐng)求或操作。
事件處理:JavaScript 中的事件處理機(jī)制可以看作是一種命令模式的應(yīng)用。當(dāng)用戶與頁面進(jìn)行交互時(shí),例如點(diǎn)擊按鈕、鍵盤按鍵或鼠標(biāo)移動(dòng)等,事件被觸發(fā)并執(zhí)行相應(yīng)的處理函數(shù)。這里事件就充當(dāng)了命令對(duì)象,而事件處理函數(shù)則扮演著命令的接收者。
XMLHttpRequest 對(duì)象:在早期的 Ajax 開發(fā)中,我們常使用 XMLHttpRequest 對(duì)象來進(jìn)行異步請(qǐng)求。開發(fā)者可以將每個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,并通過調(diào)用 send() 方法來發(fā)送請(qǐng)求。這里的 XMLHttpRequest 對(duì)象和 send() 方法即可看作是命令模式的實(shí)現(xiàn),發(fā)送請(qǐng)求的行為被封裝成具體的命令對(duì)象。
History API:瀏覽器的 History API 提供了對(duì)瀏覽器歷史記錄的控制。通過調(diào)用 pushState() 或 replaceState() 方法,我們可以添加或替換瀏覽器的歷史記錄條目,并關(guān)聯(lián)相應(yīng)的狀態(tài)數(shù)據(jù)。這里的 pushState() 和 replaceState() 方法可以看作是命令對(duì)象,用于執(zhí)行添加或替換歷史記錄的操作。
document.execCommand():Document 對(duì)象的 execCommand() 方法允許在網(wǎng)頁中執(zhí)行命令式的編輯操作,如粘貼、剪切、加粗、斜體等。開發(fā)者可以調(diào)用 execCommand() 方法并傳遞相應(yīng)的命令參數(shù)來執(zhí)行這些操作,從而實(shí)現(xiàn)富文本編輯功能。
setTimeout() 和 setInterval():JavaScript 提供了 setTimeout() 和 setInterval() 函數(shù)來實(shí)現(xiàn)定時(shí)器功能。開發(fā)者可以使用這兩個(gè)函數(shù)將一段代碼封裝成一個(gè)函數(shù)對(duì)象,并在指定的時(shí)間間隔后執(zhí)行相應(yīng)的代碼,相當(dāng)于將定時(shí)器行為封裝成具體的命令對(duì)象。
業(yè)務(wù)實(shí)踐:
- 按鈕和用戶交互:當(dāng)你需要實(shí)現(xiàn)一個(gè)具有撤銷、重做或記錄操作歷史的按鈕交互功能時(shí),可以使用命令模式。每個(gè)按鈕可以表示一個(gè)命令對(duì)象,按下按鈕時(shí)執(zhí)行相應(yīng)的命令操作。
也就是說但凡見到按鈕,都可以使用命令設(shè)計(jì)模式。
異步請(qǐng)求管理:當(dāng)你需要對(duì)異步請(qǐng)求進(jìn)行批處理、隊(duì)列化或延遲執(zhí)行時(shí),命令模式可以很好地組織和管理這些請(qǐng)求。將每個(gè)請(qǐng)求封裝成一個(gè)命令對(duì)象,并使用命令隊(duì)列來依次執(zhí)行這些命令。
菜單和快捷鍵:當(dāng)你需要實(shí)現(xiàn)復(fù)雜的菜單系統(tǒng)或支持快捷鍵操作時(shí),命令模式可以幫助你處理不同的菜單項(xiàng)或快捷鍵動(dòng)作。每個(gè)菜單項(xiàng)或快捷鍵可以關(guān)聯(lián)一個(gè)命令對(duì)象,觸發(fā)時(shí)執(zhí)行相應(yīng)的命令操作。
動(dòng)畫控制:當(dāng)你需要控制頁面元素的復(fù)雜動(dòng)畫序列或狀態(tài)切換時(shí),命令模式可以提供一種有效的方式。每個(gè)動(dòng)畫或狀態(tài)切換可以封裝成一個(gè)命令對(duì)象,通過調(diào)用者來觸發(fā)執(zhí)行。
歷史記錄與撤銷:當(dāng)你需要實(shí)現(xiàn)撤銷和重做功能或記錄用戶操作歷史時(shí),命令模式非常有用。每個(gè)用戶操作可以表示一個(gè)命令對(duì)象,并在執(zhí)行時(shí)更新狀態(tài)或記錄操作,以便支持撤銷和重做操作。
以上就是JS設(shè)計(jì)模式之命令模式的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于JS命令模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
獲取陰歷(農(nóng)歷)和當(dāng)前日期的js代碼
這篇文章主要為大家詳細(xì)介紹了獲取陰歷(農(nóng)歷)日期的js代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02javascript中不易分清的slice,splice和split三個(gè)函數(shù)
這篇文章主要為大家詳細(xì)介紹了javascript中不易分清的slice,splice和split三個(gè)函數(shù),感興趣的小伙伴們可以參考一下2016-03-03MutationObserver監(jiān)視對(duì)DOM?樹所做更改的功能妙用
這篇文章主要為大家介紹了MutationObserver監(jiān)視對(duì)DOM?樹所做更改的功能妙用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03js實(shí)現(xiàn)時(shí)間日期校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)時(shí)間日期校驗(yàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05JS實(shí)現(xiàn)圖片平面旋轉(zhuǎn)的方法
這篇文章主要介紹了JS實(shí)現(xiàn)圖片平面旋轉(zhuǎn)的方法,涉及JavaScript操作頁面元素樣式動(dòng)態(tài)變換的相關(guān)技巧,需要的朋友可以參考下2016-03-03JavaScript利用fetch實(shí)現(xiàn)異步請(qǐng)求的方法實(shí)例
傳遞信息到服務(wù)器,從服務(wù)器獲取信息,是前端發(fā)展的重中之重,尤其是現(xiàn)在前后端分離的大前提下,前后端的數(shù)據(jù)交互是前端的必修科目了,下面這篇文章主要給大家介紹了關(guān)于JavaScript利用fetch實(shí)現(xiàn)異步請(qǐng)求的相關(guān)資料,需要的朋友可以參考借鑒。2017-07-07