JS設(shè)計(jì)模式之命令模式的用法詳解
相關(guān)定義:
使用命令模式,可以將【請求的調(diào)用者】和【請求的執(zhí)行者】解耦。
調(diào)用者通過【持有命令對象】來【間接調(diào)用】接收者的方法,而無需【直接引用】接收者或了解其【具體實(shí)現(xiàn)】。
這種解耦使得我們能夠更加靈活地【擴(kuò)展】和【改變】命令的調(diào)用方式。
例如,我們可以【將命令對象保存在【隊(duì)列中】】,實(shí)現(xiàn)命令的【排隊(duì)】和【異步執(zhí)行】。
還可以記錄命令的【歷史,以支持撤銷和重做】操作。
命令模式應(yīng)用場景,【菜單操作】、【多級撤銷】、【批處理任務(wù)】等。
自我理解:
執(zhí)行者對象E:普通對象,提供了整個(gè)流程中的執(zhí)行方法,此方法對于具體的命令是可感知的,對于調(diào)用者是無感知的!
抽象命令類A:規(guī)定了具體命令的基本結(jié)構(gòu),為調(diào)用者提供統(tǒng)一的調(diào)用接口。
具體命令類S:是對抽象命令類的實(shí)現(xiàn),核心是封裝了執(zhí)行者,準(zhǔn)確來說命令只封裝了執(zhí)行者的部分方法;抽象方法的執(zhí)行本質(zhì)上是執(zhí)行了這些方法。
調(diào)用者對象C:封裝了兩個(gè)方法和一個(gè)屬性;屬性表示的是當(dāng)前命令,第一個(gè)方法是設(shè)置/切換命令具體值,第二個(gè)方法是執(zhí)行此方法。
S封裝了E,C只能接觸到S,所以C和E是解耦的
A和S的關(guān)系是一對多
解耦體現(xiàn):
C無需了解S具體內(nèi)容,只需完成觸發(fā) ;
S無需知道E怎么完成任務(wù),只需調(diào)用E暴露出來的方法即可;
S和E之間是多對多的關(guān)系,即一個(gè)S可以由多個(gè)E的部分方法組合完成,一個(gè)E也可以被不同的S封裝其上不同的部分方法;
C維護(hù)的不只是一個(gè)S,還可以是一個(gè)S隊(duì)列,當(dāng)觸發(fā)到來的時(shí)候,S隊(duì)列依次執(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è)計(jì)模式可以靈活組合網(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('請求出錯(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)建接收者對象
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è)計(jì)模式和策略設(shè)計(jì)模式的不同:
命令設(shè)計(jì)模式的最小操作單元是【命令對象】;而策略設(shè)計(jì)模式的最小操作單元是方法,或者算法。
命令設(shè)計(jì)模式一次只操作一個(gè)命令對象;而策略設(shè)計(jì)模式為了完成任務(wù)可以組合多個(gè)策略。
命令設(shè)計(jì)模式一般不會將某個(gè)命令單獨(dú)保存到內(nèi)部狀態(tài)中;而策略設(shè)計(jì)模式必須保存當(dāng)前的策略。
使用命令設(shè)計(jì)模式可以實(shí)現(xiàn)撤銷、重做等功能、這反映出各個(gè)命令之間是平等關(guān)系;而策略設(shè)計(jì)模式的各個(gè)策略之間可能是先后順序關(guān)系。
原生使用
下面這些是 JavaScript 中常見的原生部分,它們在某種程度上使用到了命令模式的思想和機(jī)制。通過封裝行為成具體的對象并在需要時(shí)進(jìn)行調(diào)用,這些原生功能可以提供更靈活、可擴(kuò)展的方式來處理相關(guān)的請求或操作。
事件處理:JavaScript 中的事件處理機(jī)制可以看作是一種命令模式的應(yīng)用。當(dāng)用戶與頁面進(jìn)行交互時(shí),例如點(diǎn)擊按鈕、鍵盤按鍵或鼠標(biāo)移動等,事件被觸發(fā)并執(zhí)行相應(yīng)的處理函數(shù)。這里事件就充當(dāng)了命令對象,而事件處理函數(shù)則扮演著命令的接收者。
XMLHttpRequest 對象:在早期的 Ajax 開發(fā)中,我們常使用 XMLHttpRequest 對象來進(jìn)行異步請求。開發(fā)者可以將每個(gè)請求封裝成一個(gè)對象,并通過調(diào)用 send() 方法來發(fā)送請求。這里的 XMLHttpRequest 對象和 send() 方法即可看作是命令模式的實(shí)現(xiàn),發(fā)送請求的行為被封裝成具體的命令對象。
History API:瀏覽器的 History API 提供了對瀏覽器歷史記錄的控制。通過調(diào)用 pushState() 或 replaceState() 方法,我們可以添加或替換瀏覽器的歷史記錄條目,并關(guān)聯(lián)相應(yīng)的狀態(tài)數(shù)據(jù)。這里的 pushState() 和 replaceState() 方法可以看作是命令對象,用于執(zhí)行添加或替換歷史記錄的操作。
document.execCommand():Document 對象的 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ù)對象,并在指定的時(shí)間間隔后執(zhí)行相應(yīng)的代碼,相當(dāng)于將定時(shí)器行為封裝成具體的命令對象。
業(yè)務(wù)實(shí)踐:
- 按鈕和用戶交互:當(dāng)你需要實(shí)現(xiàn)一個(gè)具有撤銷、重做或記錄操作歷史的按鈕交互功能時(shí),可以使用命令模式。每個(gè)按鈕可以表示一個(gè)命令對象,按下按鈕時(shí)執(zhí)行相應(yīng)的命令操作。
也就是說但凡見到按鈕,都可以使用命令設(shè)計(jì)模式。
異步請求管理:當(dāng)你需要對異步請求進(jìn)行批處理、隊(duì)列化或延遲執(zhí)行時(shí),命令模式可以很好地組織和管理這些請求。將每個(gè)請求封裝成一個(gè)命令對象,并使用命令隊(duì)列來依次執(zhí)行這些命令。
菜單和快捷鍵:當(dāng)你需要實(shí)現(xiàn)復(fù)雜的菜單系統(tǒng)或支持快捷鍵操作時(shí),命令模式可以幫助你處理不同的菜單項(xiàng)或快捷鍵動作。每個(gè)菜單項(xiàng)或快捷鍵可以關(guān)聯(lián)一個(gè)命令對象,觸發(fā)時(shí)執(zhí)行相應(yīng)的命令操作。
動畫控制:當(dāng)你需要控制頁面元素的復(fù)雜動畫序列或狀態(tài)切換時(shí),命令模式可以提供一種有效的方式。每個(gè)動畫或狀態(tài)切換可以封裝成一個(gè)命令對象,通過調(diào)用者來觸發(fā)執(zhí)行。
歷史記錄與撤銷:當(dāng)你需要實(shí)現(xiàn)撤銷和重做功能或記錄用戶操作歷史時(shí),命令模式非常有用。每個(gè)用戶操作可以表示一個(gè)命令對象,并在執(zhí)行時(shí)更新狀態(tài)或記錄操作,以便支持撤銷和重做操作。
以上就是JS設(shè)計(jì)模式之命令模式的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于JS命令模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
獲取陰歷(農(nóng)歷)和當(dāng)前日期的js代碼
這篇文章主要為大家詳細(xì)介紹了獲取陰歷(農(nóng)歷)日期的js代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
javascript中不易分清的slice,splice和split三個(gè)函數(shù)
這篇文章主要為大家詳細(xì)介紹了javascript中不易分清的slice,splice和split三個(gè)函數(shù),感興趣的小伙伴們可以參考一下2016-03-03
MutationObserver監(jiān)視對DOM?樹所做更改的功能妙用
這篇文章主要為大家介紹了MutationObserver監(jiān)視對DOM?樹所做更改的功能妙用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
js實(shí)現(xiàn)時(shí)間日期校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)時(shí)間日期校驗(yàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
JS實(shí)現(xiàn)圖片平面旋轉(zhuǎn)的方法
這篇文章主要介紹了JS實(shí)現(xiàn)圖片平面旋轉(zhuǎn)的方法,涉及JavaScript操作頁面元素樣式動態(tài)變換的相關(guān)技巧,需要的朋友可以參考下2016-03-03
JavaScript利用fetch實(shí)現(xiàn)異步請求的方法實(shí)例
傳遞信息到服務(wù)器,從服務(wù)器獲取信息,是前端發(fā)展的重中之重,尤其是現(xiàn)在前后端分離的大前提下,前后端的數(shù)據(jù)交互是前端的必修科目了,下面這篇文章主要給大家介紹了關(guān)于JavaScript利用fetch實(shí)現(xiàn)異步請求的相關(guān)資料,需要的朋友可以參考借鑒。2017-07-07

