JavaScript設計模式(七):命令模式

以樂之名發表於2019-07-14

命令模式

命令模式:請求以命令的形式包裹在物件中,並傳給呼叫物件。呼叫物件尋找可以處理該命令的合適的物件,並把該命令傳給相應的物件,該物件執行命令。

生活小栗子:客戶下單,訂單記錄了客戶購買的產品,倉庫根據訂單給客戶備貨。

模式特點

命令模式由三種角色構成:

  1. 釋出者 invoker(發出命令,呼叫命令物件,不知道如何執行與誰執行);
  2. 接收者 receiver (提供對應介面處理請求,不知道誰發起請求);
  3. 命令物件 command(接收命令,呼叫接收者對應介面處理髮布者的請求)。

命令模式

釋出者 invoker 和接收者 receiver 各自獨立,將請求封裝成命令物件 command ,請求的具體執行由命令物件 command 呼叫接收者 receiver 對應介面執行。

命令物件 command 充當釋出者 invoker 與接收者 receiver 之間的連線橋樑(中間物件介入)。實現釋出者與接收之間的解耦,對比過程化請求呼叫,命令物件 command 擁有更長的生命週期,接收者 receiver 屬性方法被封裝在命令物件 command 屬性中,使得程式執行時可任意時刻呼叫接收者物件 receiver 。因此 command 可對請求進行進一步管控處理,如實現延時、預定、排隊、撤銷等功能。

程式碼實現

class Receiver {  // 接收者類
  execute() {
    console.log('接收者執行請求');
  }
}

class Command {   // 命令物件類
  constructor(receiver) {
    this.receiver = receiver;
  }
  execute () {    // 呼叫接收者對應介面執行
    console.log('命令物件->接收者->對應介面執行');
    this.receiver.execute();
  }
}

class Invoker {   // 釋出者類
  constructor(command) {
    this.command = command;
  }
  invoke() {      // 釋出請求,呼叫命令物件
    console.log('釋出者釋出請求');
    this.command.cmd();
  }
}

const warehouse = new Receiver();       // 倉庫
const order = new Command(warehouse);   // 訂單
const client = new Invoker(order);      // 客戶
client.invoke();

/*
輸出:
  釋出者釋出請求
  命令物件->接收者->對應介面執行
  接收者執行請求
*/

應用場景

有時候需要向某些物件傳送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是什麼。需要一種鬆耦合的方式來設計程式,使得傳送者和接收者能夠消除彼此之間的耦合關係。
——《JavaScript 設計模式與開發實踐》
  1. 不關注執行者,不關注執行過程;
  2. 只要結果,支援撤銷請求、延後處理、日誌記錄等。

優缺點

  • 優點:

    • 釋出者與接收者實現解耦;
    • 可擴充套件命令,對請求可進行排隊或日誌記錄。(支援撤銷,佇列,巨集命令等功能)。
  • 缺點:

    • 額外增加命令物件,非直接呼叫,存在一定開銷。

巨集命令

巨集命令:一組命令集合(命令模式與組合模式的產物)

釋出者釋出一個請求,命令物件會遍歷命令集合下的一系列子命令並執行,完成多工。

// 巨集命令物件
class MacroCommand {
  constructor() {
    this.commandList = [];  // 快取子命令物件
  }
  add(command) {            // 向快取中新增子命令
    this.commandList.push(command);
  }
  exceute() {               // 對外命令執行介面
    // 遍歷自命令物件並執行其 execute 方法
    for (const command of this.commandList) {
      command.execute();
    }
  }
}

const openWechat = {  // 命令物件
  execute: () => {
    console.log('開啟微信');
  }
};

const openChrome = {  // 命令物件
  execute: () => {
    console.log('開啟Chrome');
  }
};

const openEmail = {   // 命令物件
  execute: () => {
    console.log('開啟Email');
  }
}

const macroCommand = new MacroCommand();

macroCommand.add(openWechat); // 巨集命令中新增子命令
macroCommand.add(openChrome); // 巨集命令中新增子命令
macroCommand.add(openEmail);  // 巨集命令中新增子命令

macroCommand.execute();       // 執行巨集命令
/* 輸出:
開啟微信
開啟Chrome
開啟Email
*/

傻瓜命令與智慧命令

傻瓜命令:命令物件需要接收者來執行客戶的請求。

智慧命令:命令物件直接實現請求,不需要接收者,“聰明”的命令物件。

“傻瓜命令” 與 “智慧命令” 的區別在於是否有 “接收者” 物件。

// openWechat 是智慧命令物件,並沒有傳入 receiver 接收物件
const openWechat = {
  execute: () => {  // 命令物件直接處理請求
    console.log('開啟微信');
  }
};

沒有 “接收者” 的智慧命令與策略模式很類似。程式碼實現類似,區別在於實現目標不同。

  1. 策略模式中實現的目標是一致的,只是實現演算法不同(如目標:根據KPI計算獎金);
  2. 智慧命令的解決問題更廣,目標更具散發性。(如目標:計算獎金/計算出勤率等)。

參考文章

本文首發Github,期待Star!
https://github.com/ZengLingYong/blog

作者:以樂之名
本文原創,有不當的地方歡迎指出。轉載請指明出處。

相關文章