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