iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0

命令模式將請求的行為封裝成物件,發送請求的物件只需要依賴於命令介面而不需要知道背後的實作細節。

生活案例

想像你在得來速點餐,櫃檯人員透過點餐機記下餐點,點餐機將訂單送到廚房,廚師再依據訂單上的內容製作餐點。過程中,櫃檯人員只負責建立訂單,他不必料理餐點,也不需要知道餐點由誰製作。

在這個例子中,櫃檯人員就像發送請求的物件,訂單則是命令物件。櫃檯人員只需要利用點餐機建立訂單命令,廚師就會依據訂單準備餐點,兩者之間不需要額外的溝通,很方便是吧!

舉個例子

想像我們正在為作業系統開發一個快捷鍵管理功能,這項功能允許使用者自訂鍵盤快捷鍵,例如一鍵開啟 Spotify,或透過快捷鍵快速爬取最新的股票資料。使用者可以針對常用任務設置快捷鍵,從而提升個人化的操作體驗。

為了實現這樣的功能,我們可以利用命令模式定義通用的命令介面,並讓所有任務都實現該介面。這樣,快捷鍵管理功能只需依賴這個統一的命令介面,而無需關心每項任務的具體實作。如此一來,當我們需要新增或修改快捷鍵時,只需調整相應的命令,而不必更改管理功能的核心程式碼,大幅提升了程式碼的擴展性與維護性。

定義命令介面

首先,定義一個命令介面,讓所有具體命令都能透過共通的 execute 方法執行。

interface Command {
  execute(): void;
}

建立具體命令

定義一些具體的命令來模擬真實情境,如播放音樂和打開編輯器。

class MusicOnCommand implements Command {
  execute() {
    console.log("Open Spotify");
    console.log("Start playing music");
  }
}

class EditorStartCommand implements Command {
  execute() {
    console.log("Open VS Code");
  }
}

class TodoListOpenCommand implements Command {
  execute() {
    console.log("Open Notion");
    console.log("Show todo list");
  }
}

我們可以透過巨集命令將一組命令集合起來,以便一次執行多個命令。

class MacroCommand implements Command {
  constructor(private commands: Command[]) {}

  execute() {
    for (const command of this.commands) {
      command.execute();
    }
  }
}

實作快捷鍵管理功能

定義一個類別來管理快捷鍵和命令。

class HotkeyRegistry {
  private commands: Map<string, Command>;

  constructor() {
    this.commands = new Map();
  }

  getHotkey(keystrokes: string) {
    return this.commands.get(keystrokes);
  }

  setHotkey(keystrokes: string, command: Command) {
    this.commands.set(keystrokes, command);
  }
}

讓鍵盤監聽輸入事件,每當使用者按下某個快捷鍵就會自動執行對應的命令。

class Keyboard {
  constructor(private hotkeys: HotkeyRegistry) {}

  setHotkey(keystrokes: string, command: Command) {
    this.hotkeys.setHotkey(keystrokes, command);
  }

  onKeyPress(keystrokes: string) {
    const command = this.hotkeys.getHotkey(keystrokes);
    if (command) {
      command.execute();
    }
  }
}

執行測試程式

下面是一段測試程式,展現了如何透過自訂快捷鍵來播放音樂並開啟工作模式。

class KeyboardTestDrive {
  static main() {
    const hotkeys = new HotkeyRegistry();
    const keyboard = new Keyboard(hotkeys);

    const musicOn = new MusicOnCommand();
    const editorStart = new EditorStartCommand();
    const todoListOpen = new TodoListOpenCommand();
    const workStart = new MacroCommand([editorStart, todoListOpen]);

    keyboard.setHotkey("Ctrl+M", musicOn);
    keyboard.setHotkey("Ctrl+W", workStart);

    console.log("Press 'Ctrl+M':");
    keyboard.onKeyPress("Ctrl+M");
    console.log("\nPress 'Ctrl+W':");
    keyboard.onKeyPress("Ctrl+W");
  }
}

KeyboardTestDrive.main();

執行結果。

Press 'Ctrl+M':
Open Spotify
Start playing music

Press 'Ctrl+W':
Open VS Code
Open Notion
Show todo list

定義

Command Pattern

  • 抽象命令(Command): 定義共通的執行方法
  • 具體命令(ConcreteCommand): 定義具體的行為
  • 調用者(Invoker): 負責設定與調用命令

命令模式將行為封裝成獨立的命令物件,並統一它們的執行方法,讓客戶端程式碼透過共通的介面來執行程式,而不需要在意具體的實作細節。命令物件能夠輕鬆地被傳遞、儲存以及執行,這不僅提升了行為的重用性,也能用於實現佇列、日誌和復原等功能。

總結

  • 發送請求的物件不需要知道請求的接收者與背後的實作細節
  • 命令物件能夠輕鬆地被傳遞、儲存與調用
  • 只要滿足命令介面,任何動作都可以成為命令物件
  • 使用者可以在執行時期動態地指派命令物件

完整範例

https://github.com/chengen0612/design-patterns-typescript/blob/main/patterns/behavioral/command.ts


上一篇
Day 10 - Singleton 單例
下一篇
Day 12 - Facade 表象
系列文
前端也想學設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言