全能指揮官:玩轉(zhuǎn)JavaScript命令模式,讓代碼聽你的話厂财!
命令模式的含義
- 命令模式指的是一個(gè)執(zhí)行某些特定的指令芋簿。
- 命令模式的示例 demo:
// 命令接口
class Command {
execute() {}
}
// 具體命令:打開文檔
class OpenDocumentCommand extends Command {
constructor(document) {
super();
this.document = document;
}
// 執(zhí)行打開文檔命令
execute() {
this.document.open();
}
}
// 具體命令:保存文檔
class SaveDocumentCommand extends Command {
constructor(document) {
super();
this.document = document;
}
// 執(zhí)行保存文檔命令
execute() {
this.document.save();
}
}
// 接收者:文檔
class Document {
open() {
console.log("打開文檔");
}
save() {
console.log("保存文檔");
}
}
// 調(diào)用者:按鈕
class Button {
constructor(command) {
this.command = command;
}
// 點(diǎn)擊按鈕執(zhí)行命令
click() {
this.command.execute();
}
}
// 使用示例
const document = new Document();
// 創(chuàng)建打開文檔命令并關(guān)聯(lián)文檔對(duì)象
const openCommand = new OpenDocumentCommand(document);
// 創(chuàng)建保存文檔命令并關(guān)聯(lián)文檔對(duì)象
const saveCommand = new SaveDocumentCommand(document);
// 創(chuàng)建按鈕并關(guān)聯(lián)打開文檔命令
const openButton = new Button(openCommand);
// 創(chuàng)建按鈕并關(guān)聯(lián)保存文檔命令
const saveButton = new Button(saveCommand);
// 點(diǎn)擊按鈕執(zhí)行命令
openButton.click(); // 打開文檔
saveButton.click(); // 保存文檔
命令模式的特點(diǎn)
- 在命令模式中,Command 對(duì)象擁有更長的生命周期璃饱。還支持撤銷与斤,排隊(duì)等操作。而設(shè)計(jì)模式的主題總是會(huì)把不變的事物和變化的事物分離出來荚恶,命令模式也不例外撩穿。
JavaScript 中的命令模式
- 所謂的命令模式,就是給對(duì)象的某個(gè)方法取一個(gè) execute 的名字谒撼,引入 command 對(duì)象和 receiver 這兩個(gè)無中生有的角色無非是把簡單的事情復(fù)雜化了食寡。
- 命令模式的由來,其實(shí)是回調(diào)(callback)函數(shù)的一個(gè)面向?qū)ο蟮奶娲贰?/li>
宏命令
- 宏命令是一組命令的集合廓潜,可通過宏命令的方式冻河,可一次執(zhí)行一批命令。
完整版demo:
// 命令接口
class Command {
execute() {}
undo() {}
redo() {}
}
// 具體命令:打開文檔
class OpenDocumentCommand extends Command {
constructor(document) {
super();
this.document = document;
}
execute() {
this.document.open();
}
undo() {
this.document.close();
}
redo() {
this.execute();
}
}
// 具體命令:保存文檔
class SaveDocumentCommand extends Command {
constructor(document) {
super();
this.document = document;
}
execute() {
this.document.save();
}
undo() {
// 撤銷保存操作茉帅,恢復(fù)文檔到上一個(gè)保存點(diǎn)或初始狀態(tài)
this.document.restore();
}
redo() {
this.execute();
}
}
// 接收者:文檔
class Document {
constructor(name) {
this.name = name;
this.content = "";
this.savedContent = "";
}
open() {
console.log(`打開文檔:${this.name}`);
}
close() {
console.log(`關(guān)閉文檔:${this.name}`);
}
save() {
this.savedContent = this.content;
console.log(`保存文檔:${this.name}`);
}
restore() {
this.content = this.savedContent;
console.log(`恢復(fù)文檔:${this.name}`);
}
setContent(content) {
this.content = content;
console.log(`設(shè)置文檔內(nèi)容:${this.name}`);
}
getContent() {
return this.content;
}
}
// 調(diào)用者:按鈕
class Button {
constructor() {
this.commandQueue = [];
this.undoStack = [];
this.redoStack = [];
}
// 將命令加入隊(duì)列
addToQueue(command) {
this.commandQueue.push(command);
}
// 執(zhí)行隊(duì)列中的命令
executeQueue() {
console.log("執(zhí)行命令隊(duì)列:");
while (this.commandQueue.length > 0) {
const command = this.commandQueue.shift();
command.execute();
this.undoStack.push(command);
}
}
// 撤銷上一次執(zhí)行的命令
undo() {
if (this.undoStack.length === 0) {
console.log("沒有可撤銷的命令");
return;
}
const command = this.undoStack.pop();
command.undo();
this.redoStack.push(command);
console.log("撤銷上一次命令");
}
// 重做上一次撤銷的命令
redo() {
if (this.redoStack.length === 0) {
console.log("沒有可重做的命令");
return;
}
const command = this.redoStack.pop();
command.redo();
this.undoStack.push(command);
console.log("重做上一次撤銷的命令");
}
}
// 使用示例
const document = new Document("example.txt");
// 創(chuàng)建按鈕
const button = new Button();
// 創(chuàng)建打開文檔命令并關(guān)聯(lián)文檔對(duì)象
const openCommand = new OpenDocumentCommand(document);
// 創(chuàng)建保存文檔命令并關(guān)聯(lián)文檔對(duì)象
const saveCommand = new SaveDocumentCommand(document);
// 將命令加入隊(duì)列
button.addToQueue(openCommand);
button.addToQueue(saveCommand);
// 執(zhí)行命令隊(duì)列
button.executeQueue();
// 撤銷命令
button.undo();
// 重做命令
button.redo();
- 在這個(gè)示例中叨叙,我們首先定義了命令接口Command,其中包含execute堪澎、undo和redo方法擂错。然后創(chuàng)建了兩個(gè)具體的命令類OpenDocumentCommand和SaveDocumentCommand,它們分別實(shí)現(xiàn)了命令的執(zhí)行樱蛤、撤銷和重做操作钮呀。
- 接著,我們定義了文檔類Document作為接收者昨凡,其中包含了打開文檔爽醋、關(guān)閉文檔、保存文檔和恢復(fù)文檔的操作便脊。
- 然后蚂四,我們創(chuàng)建了調(diào)用者類Button,它包含命令隊(duì)列、撤銷棧和重做棧的管理遂赠。通過addToQueue方法將命令加入隊(duì)列久妆,executeQueue方法執(zhí)行隊(duì)列中的命令,undo方法撤銷上一次執(zhí)行的命令跷睦,redo方法重做上一次撤銷的命令筷弦。
- 在示例的最后,我們創(chuàng)建了文檔對(duì)象抑诸、按鈕對(duì)象烂琴,并關(guān)聯(lián)了打開文檔和保存文檔命令。然后將命令加入隊(duì)列蜕乡,執(zhí)行命令隊(duì)列监右,撤銷命令,重做命令异希。
命令模式的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):
- 解耦發(fā)送者和接收者:命令模式通過將請(qǐng)求封裝為命令對(duì)象健盒,將發(fā)送者和接收者解耦。發(fā)送者只需要知道如何觸摸命令称簿,而不需要關(guān)心具體的接收者和執(zhí)行操作扣癣。
- 易擴(kuò)展:由于命令模式將請(qǐng)求封裝成了獨(dú)立的命令對(duì)象,因此添加一個(gè)命令只需要實(shí)現(xiàn)一個(gè)新的命令的類憨降,不需要修改原有的代碼結(jié)構(gòu)
- 支持隊(duì)列化和延遲執(zhí)行:命令模式將多個(gè)命令對(duì)象組合成一個(gè)命令隊(duì)列(宏命令)父虑,實(shí)現(xiàn)批量執(zhí)行和撤銷操作。也可以實(shí)現(xiàn)延遲執(zhí)行授药,將命令對(duì)象存儲(chǔ)起來士嚎,在需要的時(shí)候在執(zhí)行。
- 支持撤銷和重做:通過保存命令的執(zhí)行歷史悔叽,可實(shí)現(xiàn)撤銷和重做操作莱衩,對(duì)于用戶操作的回退和恢復(fù)非常有用。
- 支持日志和記錄:可記錄命令的執(zhí)行日志娇澎,用于系統(tǒng)的跟蹤和調(diào)試笨蚁。
- 缺點(diǎn):
- 增加了類的數(shù)量:隨著引入命令模式的增加,會(huì)導(dǎo)致類的數(shù)量增加趟庄,增加代碼的復(fù)雜性括细。
- 命令執(zhí)行效率降低:由于將命令模式需要封裝成對(duì)象,因此會(huì)增加一定的執(zhí)行開銷戚啥,對(duì)于性能要求較高的場景可能會(huì)有影響奋单。
命令模式的適用場景
- 撤銷和重做
- 異步任務(wù)處理:若在后臺(tái)處理數(shù)據(jù)或執(zhí)行長時(shí)間運(yùn)行的操作。
- 日志記錄和系統(tǒng)操作記錄
- 隊(duì)列和調(diào)度任務(wù):可將命令對(duì)象添加到隊(duì)列中猫十,然后按照隊(duì)列中的順序依次執(zhí)行览濒。
命令模式的最佳實(shí)踐
- 封裝命令:將每個(gè)操作封裝為獨(dú)立的命令對(duì)象
- 使用接口和抽象類:定義一個(gè)接口和抽象類來表示命令對(duì)象呆盖,以確保命令對(duì)象具有抑制的方法和屬性
- 參數(shù)化命令:在命令對(duì)象中傳遞參數(shù),使命令對(duì)象能夠執(zhí)行不同的操作
- 解耦調(diào)用者和接收者:調(diào)用者只需要知道如何觸發(fā)命令匾七,而不需要了解命令的具體實(shí)現(xiàn)絮短。
- 支持的撤銷操作
- 宏命令
- 單一職責(zé)原則:每個(gè)命令對(duì)象應(yīng)該只負(fù)責(zé)一個(gè)具體的操作或請(qǐng)求江兢,這樣可以確保命令對(duì)象的職責(zé)清晰昨忆,代碼結(jié)構(gòu)清晰。
- 代碼的靈活性和可擴(kuò)展性
特殊字符描述:
- 問題標(biāo)注
Q:(question)
- 答案標(biāo)注
R:(result)
- 注意事項(xiàng)標(biāo)準(zhǔn):
A:(attention matters)
- 詳情描述標(biāo)注:
D:(detail info)
- 總結(jié)標(biāo)注:
S:(summary)
- 分析標(biāo)注:
Ana:(analysis)
- 提示標(biāo)注:
T:(tips)