深入理解JavaScript設計模式之命令模式

深入理解JavaScript設計模式之命令模式

深入理解JavaScript設計模式之命令模式

文章目錄

  • 深入理解JavaScript設計模式之命令模式
    • 定義
    • 簡單命令模式
    • 組合命令模式
    • 使用命令模式實現文本編輯器
      • 目標
      • 關鍵類說明
      • 實現的效果
      • 交互邏輯流程
      • 所有代碼:
    • 總結

定義

命令模式也是設計模式種相對于變焦簡單容易理解的一種設計模式。

JavaScript中,命令模式用于將一個請求或簡單操作封裝為一個對象。這使得你可以使用不同的請求、隊列請求或者記錄請求日志、撤銷操作等。命令模式通常用于實現諸如撤銷/重做功能、事務系統以及在復雜對象間傳遞請求等場景。

白話說就是:有時候需要向某些對象發送請求,但是并不知道請求的接收者是誰,也不知道被請求的操作是什么。使得請求發送者和請求接收者能夠消除彼此之間的耦合關系,命令模式還支持撤銷、排隊等操作。

簡單命令模式

定義很難了解命令模式的用處,舉個開燈關燈的命令模式例子,如下,定義了兩個LightOnCommand LightOffCommand 兩個命令分別執行light 對象中的onoff方法,在new LightOnCommand的時候將light作為參數傳入并執行LightOnCommand.execute();的方法實現開燈關燈的操作。

<body><button id="btn">按鈕</button>
</body>
<script>/*** 點擊按鈕,執行開燈關燈的操作*/const light = {on() {console.log("開燈");},off() {console.log("關燈");},};class LightOnCommand {constructor(light) {this.light = light;}execute() {this.light.on();}}class LightOffCommand {constructor(light) {this.light = light;}execute() {this.light.off();}}const onLight = new LightOnCommand(light);const offLight = new LightOffCommand(light);let isOn = true;document.getElementById("btn").addEventListener("click", function () {(isOn ? onLight : offLight).execute();isOn = !isOn;});
</script>

深入理解JavaScript設計模式之命令模式
一開始學的時候覺得這就是脫褲子放屁多此一舉,但是仔細還差與思考,使用這種方式,可以讓代碼更加模塊化與更容易維護。

  • 解耦:調用者和接收者之間解耦,調用者不需要知道接收者的具體實現。
  • 擴展性:可以很容易地添加新的命令而不需要修改現有的類。
  • 可撤銷操作:可以通過記錄命令的歷史來實現撤銷操作。
  • 隊列請求:可以將命令存儲在隊列中,按順序執行。
  • 日志記錄:可以記錄命令的歷史,便于調試和回溯。

組合命令模式

第一個簡單的例子可以看到點擊按鈕只執行了一次命令,如果有多條命令,那就可以將多個命令添加到Command 里的一個stack 數組中,最后執行Command.execute的時候遍歷stack數組中的命令統一遍歷執行。
頁面中有一個按鈕 #btn,當點擊按鈕時,依次執行以下三個命令:

  1. 開燈(LightOnCommand)
  2. 工人開始工作并停止(WorkerCommand)
  3. 關燈(LightOffCommand)

這些命令被添加到一個 Command 對象中,并在點擊事件發生時統一執行,代碼如下:

<body><button id="btn">按鈕</button>
</body>
<script>class Command {constructor() {this.stack = [];}add(command) {this.stack.push(command);}execute() {this.stack.forEach((command) => command.execute());}}const light = {on: () => console.log("開燈"),off: () => console.log("關燈"),};const worker = {do: () => console.log("開始工作"),stop: () => console.log("停止工作"),};class WorkerCommand {constructor(worker) {this.worker = worker;}execute() {this.worker.do();this.worker.stop();}}// 命令拆分class LightOnCommand {constructor(light) {this.light = light;}execute() {this.light.on();}}class LightOffCommand {constructor(light) {this.light = light;}execute() {this.light.off();}}const command = new Command();command.add(new LightOnCommand(light));command.add(new WorkerCommand(worker));command.add(new LightOffCommand(light));document.getElementById("btn").addEventListener("click", () => {command.execute();});
</script>

深入理解JavaScript設計模式之命令模式
這種寫法的優點:

  1. 解耦調用者與執行者 按鈕點擊事件(調用者)并不直接調用 light.on()worker.do(),而是交給命令對象去處理。 這樣使得界面邏輯和業務邏輯分離,提高了可維護性。
  2. 易于擴展新的命令 如果需要新增功能,比如“打開風扇”或“播放音樂”,只需要定義一個新的命令類并加入命令隊列即可,不需要修改已有代碼。 符合 開放封閉原則(OCP):對擴展開放,對修改關閉。
  3. 支持組合命令 Command類中的 stack 可以保存多個命令,可以輕松實現宏命令(一組命令的集合),如示例中的一鍵執行開燈、工作、關燈等操作。 后續也可以支持撤銷/重做等功能(只需記錄歷史棧)。
  4. 便于測試與復用 每個命令是獨立的對象,可以單獨測試其 execute() 方法。 命令可以在不同上下文中復用,例如在定時器中觸發、遠程調用等。
  5. 提升代碼可讀性和結構清晰度 將每個操作抽象為類,有助于理解意圖(Intent)。 比如看到 new LightOnCommand(light),就知道這是“開燈”的命令,比直接調用函數更具語義化。

總的來說:通過組合命令模式可以實現良好的職責分離,靈活擴展和統一控制,如果需求遇到了對多個操作進行封裝調度記錄和撤銷的時候,可以使用組合命令實現。

使用命令模式實現文本編輯器

如下舉例加深命令模式的使用,如下我想實現一個文本編輯器,其中功能有【清空內容轉為大寫轉為小寫撤銷重做指令列表
深入理解JavaScript設計模式之命令模式

目標

實現一個基于命令模式的文本編輯器,具備【清空內容轉為大寫轉為小寫撤銷重做指令列表顯示每一步操作的命令記錄

關鍵類說明

  • Editor(接收者):
class Editor {constructor() {this.content = "";}
}

存儲當前文本內容,所用命令的實際執行者。

  • TextChangeCommand(基礎命令)
class TextChangeCommand {constructor(editor, newText) {this.editor = editor;this.newText = newText;this.previousText = editor.content;}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}
}

表示每次文本輸入變更的操作,記錄修改前后的狀態,支持撤銷。

  • CommandManager(擴展命令)
class CommandManager {constructor() {this.tack = [];}execute(command) {if (command) {this.tack.push(command);command.execute();updateUI();}}// 清空redo() {this.tack = [];updateUI();}// 撤銷undo() {if (this.tack.length > 0) {const command = this.tack.pop();command.undo();updateUI();} else {console.log("沒有可撤銷的命令");updateUI();return;}}// 查看命令列表getTackList() {return this.tack;}}

使用棧(tack)保存所有已執行命令,提供 execute()undo()redo()getTackList() 方法,控制整個命令流程。

  • UpperCaseCommand(命令管理器)
class UpperCaseCommand {
constructor(editor) {this.editor = editor;this.previousText = editor.content;this.newText = editor.content.toUpperCase();}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}
}

將文本轉為大寫的命令,同樣支持撤銷,可以繼續擴展更多命令如 LowerCaseCommand,ClearCommand 等。

實現的效果

深入理解JavaScript設計模式之命令模式

交互邏輯流程

  1. 初始化
    創建 EditorCommandManager,設置初始文本為空,綁定 DOM 元素(如 textarea、按鈕)。
  2. 用戶操作觸發命令,輸入文字 → 觸發input事件 → 創建TextChangeCommand → 執行并入棧
    點擊按鈕(清空、大寫、小寫)→ 創建對應命令 → 執行并入棧。
  3. 撤銷 / 重做,“撤銷”點擊 → 從棧中彈出最后一個命令 → 調用 .undo(),“重做”點擊 → 清空棧(當前簡單實現) 當前重做只是清空棧,沒有真正實現“恢復撤銷”的動作,可進一步改進。

所有代碼:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>命令模式文本編輯器</title><style>body {margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;background: linear-gradient(45deg, #ff6b6b, #c471ad);font-family: Arial, sans-serif;}.editor-container {width: 300px;background: white;border-radius: 5px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);}.header {background: #2c3e50;color: white;text-align: center;padding: 10px 0;border-top-left-radius: 5px;border-top-right-radius: 5px;}.content {padding: 20px;height: 150px;border-bottom: 1px solid #ddd;}.buttons {display: flex;justify-content: space-around;padding: 10px;}.buttons button {padding: 8px 15px;border: none;border-radius: 3px;cursor: pointer;}.buttons .primary {background: #3498db;color: white;}.buttons .secondary {background: #ecf0f1;color: #333;}#contentText {border: none;height: 150px;width: 100%;}#tackListView {border: none;height: 130px;width: 100%;}</style></head><body><div class="editor-container"><div class="header">命令模式文本編輯器</div><div class="content"><!-- 文本編輯區域 --><textarea id="contentText"></textarea></div><div class="buttons"><button class="primary" id="clearBtn">清空內容</button><button class="primary" id="upperBtn">轉為大寫</button><button class="primary" id="lowerBtn">轉為小寫</button></div><div class="buttons"><button class="secondary" id="undoBtn">撤銷</button><button class="secondary" id="redoBtn">重做</button><button class="secondary" id="stackList">指令列表</button></div><div class="content">命令列表:<textarea id="tackListView"></textarea></div></div><script>class TextChangeCommand {constructor(editor, newText) {this.editor = editor;this.newText = newText;this.previousText = editor.content;}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}}class CommandManager {constructor() {this.tack = [];}execute(command) {if (command) {this.tack.push(command);command.execute();updateUI();}}// 清空redo() {this.tack = [];updateUI();}// 撤銷undo() {if (this.tack.length > 0) {const command = this.tack.pop();command.undo();updateUI();} else {console.log("沒有可撤銷的命令");updateUI();return;}}// 查看命令列表getTackList() {return this.tack;}}class UpperCaseCommand {constructor(editor) {this.editor = editor;this.previousText = editor.content;this.newText = editor.content.toUpperCase();}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}}// 接收者class Editor {constructor() {this.content = "";}}// 初始化const editor = new Editor();const commandManager = new CommandManager();// DOM元素const textarea = document.getElementById("contentText");// 設置初始內容editor.content = textarea.value;// 事件監聽textarea.addEventListener("input", function () {const command = new TextChangeCommand(editor, textarea.value);commandManager.execute(command);});document.getElementById("clearBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor, "");commandManager.execute(command);});document.getElementById("upperBtn").addEventListener("click", function () {const command = new UpperCaseCommand(editor);commandManager.execute(command);});document.getElementById("lowerBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor,textarea.value.toLowerCase());commandManager.execute(command);});document.getElementById("undoBtn").addEventListener("click", function () {commandManager.undo();});document.getElementById("redoBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor, "");commandManager.execute(command);commandManager.redo();});document.getElementById("stackList").addEventListener("click", function () {console.log(commandManager.getTackList());});// 更新UIfunction updateUI() {// 更新主文本區域textarea.value = editor.content;// 獲取命令列表顯示區域const tackListView = document.getElementById("tackListView");// 獲取當前命令棧const commands = commandManager.getTackList();// 格式化命令記錄let logText = "";for (let i = 0; i < commands.length; i++) {const cmd = commands[i];if (cmd instanceof TextChangeCommand) {logText += `${i + 1}. 文本修改為: ${cmd.newText}\n`;} else if (cmd instanceof UpperCaseCommand) {logText += `${i + 1}. 轉為大寫: ${cmd.newText}\n`;}}// 如果沒有命令,顯示提示信息if (commands.length === 0) {logText = "暫無命令記錄";}// 更新命令列表顯示區域tackListView.value = logText;}// 初始化UI更新updateUI();</script></body>
</html>

總結

設計模式不是“炫技”,而是"沉淀",希望通過閱讀和學習《JavaScript設計模式》和實踐中,在顯示業務需求開發中寫出更具有可維護性,可擴展性的代碼。

致敬—— 《JavaScript設計模式》· 曾探

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87912.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87912.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87912.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CSS 網頁布局:從基礎到進階

CSS 網頁布局&#xff1a;從基礎到進階 引言 隨著互聯網的飛速發展&#xff0c;網頁設計已經成為了一個不可或缺的領域。CSS&#xff08;層疊樣式表&#xff09;作為網頁設計中的關鍵工具&#xff0c;用于控制網頁元素的樣式和布局。本文將為您全面解析CSS網頁布局&#xff0c;…

【人工智能】大語言模型(LLM) NLP

大語言模型&#xff08;LLM&#xff09;& NLP1.大語言模型&#xff08;LLM&#xff09;1.1 一句話解釋1.2 更形象的比喻1.3 為什么叫 “大” 模型1.4 它能做什么1.5 現實中的例子2.對比 NLP2.1 用 “汽車進化” 比喻 NLP → LLM2.2 為什么說 LLM 屬于 NLP2.3 LLM 的 “革命…

Unity HDRP + Azure IoT 的 Python 后端實現與集成方案

Unity HDRP Azure IoT 的 Python 后端實現與集成方案 雖然Unity HDRP本身使用C#開發&#xff0c;但我們可以構建Python后端服務支持物聯網系統&#xff0c;并與Unity引擎深度集成。以下是完整的實現方案&#xff1a; 系統架構 #mermaid-svg-qCDb0g9Ik287Cg8X {font-family:&qu…

小黑黑日常積累大模型prompt句式2:【以段落的形式輸出,不分點列舉】【如果沒有相關內容則不輸出】【可讀性強】【輸出格式規范】

以段落的形式輸出&#xff0c;不分點列舉 每個標題下直接接續段落內容&#xff0c;不編號、不分點。......標題下直接接續段落內容&#xff0c;不繼續進行分點列舉。如果沒有相關內容則不輸出 若某一部分無法從原文中提取有效信息&#xff0c;則跳過該部分內容&#xff0c;不做…

React Native 基礎組件詳解<一>

一、Text組件 1&#xff09;numberOfLines&#xff1a;顯示行數 2&#xff09;ellipsizeMode&#xff1a;超出隱藏的位置 clip->裁掉 head/middle/ tail->點的位置 3&#xff09;selectable: 是否可以選中 4&#xff09;selectionColor&#xff1a;選中后的顏色 5&#…

異步編程(Promise/Generator/async)

1、Promise 2、Generator 3、async/await

【Note】《Kafka: The Definitive Guide》 第8章: Cross-Cluster Data Mirroring

《Kafka: The Definitive Guide》 第8章&#xff1a; Cross-Cluster Data Mirroring 一、跨集群鏡像的場景與價值 多區域低延遲訪問 將業務數據從主集群實時復制到多個地理區域的集群&#xff0c;縮短消費者跨區讀取延遲。 災備切換 當主集群出現故障時&#xff0c;可快速將消…

「Windows/Mac OS」AIGC圖片生成視頻 ,webui + stable-diffusion環境部署教程

stable-diffusion webui 環境搭建目錄 一、Windows 環境部署 stable-diffusion-webui1、準備條件2、安裝Python 3.10.X&#xff08;**較新版本的 Python 不支持 torch**&#xff09;3、安裝Git 教程4、使用Git 下載 stable-diffusion-webui 存儲庫&#xff0c;4.1、顯示報錯 5…

【深度學習】 深度學習訓練配置參數詳解

深度學習訓練配置參數詳解 1. 啟動初始化參數說明CUDA_VISIBLE_DEVICES指定使用的GPU設備編號&#xff08;"0"表示單卡&#xff09;seed隨機種子&#xff08;1777777&#xff09;&#xff0c;保證實驗可復現性cuda是否啟用GPU加速&#xff08;True&#xff09;benchm…

期望,積分,均值,求和的關系

1. 回顧期望的定義 對于連續性隨機變量 X X X&#xff0c;期望為&#xff1a; E X ~ f ( x ) [ X ] ∫ Ω x f ( x ) d x E_{X\sim f(x)}[X] \int_{\Omega}xf(x)dx EX~f(x)?[X]∫Ω?xf(x)dx 其中 f ( x ) f(x) f(x)為概率密度函數&#xff0c; Ω \Omega Ω為概率密度函…

1.如何對多個控件進行高效的綁定 C#例子 WPF例子

使用ObservableCollection高效為多個控件綁定數據在WPF開發中&#xff0c;數據綁定是一個非常重要的功能&#xff0c;它允許我們將UI控件與數據源進行綁定&#xff0c;從而實現數據的自動更新。當需要為多個控件綁定數據時&#xff0c;使用ObservableCollection可以大大提高開發…

JSONLines和JSON數據格式使用教程

文章目錄 一、核心區別二、JSONLines 的優勢三、Python 中使用 JSONLines1. 寫入 JSONLines 文件2. 讀取 JSONLines 文件3. 處理大文件示例四、常見工具支持1. 命令行工具2. 編程語言庫五、適用場景選擇六、注意事項總結JSONLines(簡稱 jsonl 或 jl)和傳統 JSON 都是用于存儲…

鏈表算法之【反轉鏈表】

目錄 LeetCode-206題 LeetCode-206題 給定一個單鏈表的頭節點&#xff0c;請反轉鏈表&#xff0c;并返回反轉后的鏈表 class Solution {public ListNode reverseList(ListNode head) {// checkif (head null || head.next null)return head;// 雙指針ListNode p1 head;Li…

回溯題解——子集【LeetCode】輸入的視角(選或不選)

78. 子集 ? 一、算法邏輯講解&#xff08;逐步思路&#xff09; 邏輯講解&#xff1a; dfs(i)&#xff1a;表示從下標 i 開始&#xff0c;做“選 or 不選”的子集構造。 終止條件 if i n&#xff1a; 到達數組末尾&#xff0c;表示一種完整子集構造完成。 把當前構造路徑…

使用Electron開發跨平臺本地文件管理器:從入門到實踐

在當今數字化時代&#xff0c;文件管理是每個計算機用戶日常工作中不可或缺的一部分。雖然操作系統都提供了自己的文件管理器&#xff0c;但開發一個自定義的文件管理器可以帶來更好的用戶體驗、特定功能的集成以及跨平臺的一致性。本文將詳細介紹如何使用Electron框架構建一個…

JBHI 2025 | 潛在擴散模型賦能胸部X射線骨抑制

Abstract: 肺部疾病是全球健康面臨的一項重大挑戰&#xff0c;胸部 X 光檢查&#xff08;CXR&#xff09;因其方便性和經濟性而成為一種重要的診斷工具。 然而&#xff0c;CXR 圖像中重疊的骨結構往往會阻礙肺部病變的檢測&#xff0c;從而導致潛在的誤診。 為解決這一問題&am…

408第三季part2 - 計算機網絡 - 計算機網絡基本概念

理解然后區分一下這2個區別特點是建立連接存儲轉發的意思是A先發給B&#xff0c;B再發給C&#xff0c;就這樣這里缺點比如A很大&#xff0c;你給B緩存開銷大還需要排序然后形象的圖題目分組頭部要放一些源地址和目的地址這些東西以后發數據只會往近的發&#xff0c;不可能往下面…

互補功率放大器Multisim電路仿真——硬件工程師筆記

目錄 1 互補功率放大器基礎知識 1.1 工作原理 1.2 電路結構 1.3 優點 1.4 缺點 1.5 應用 1.6 總結 2 OCL乙類互補功率放大電路 2.1 電路結構 2.2 工作原理 2.3 優點 2.4 缺點 2.5 總結 3 OCL甲乙類互補功率放大電路 3.1 電路結構 3.2 工作原理 3.3 優點 3.4 …

【1】確認安裝 Node.js 和 npm版本號

搭建前端項目時需要安裝 Node.js 和 npm&#xff0c;主要是因為它們提供了一些重要的功能和工具&#xff0c;幫助開發者高效地開發、構建和管理項目。一、具體原因如下&#xff1a; Node.js&#xff1a;JavaScript 運行環境 Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運…

7、從網絡中獲取數據

目錄 訂閱網絡狀態變化創建網絡對象獲取默認激活網絡及其能力可訂閱事件可訂閱事件——網絡可用事件可訂閱事件——網絡阻塞狀態事件可訂閱事件——網絡能力變化事件可訂閱事件——網絡連接信息變化事件可訂閱事件——網絡丟失事件常見事件訂閱場景 開發流程 使用HTTP訪問網絡發…