1. 程式人生 > 實用技巧 >JavaScript設計模式之命令模式【命令解耦】

JavaScript設計模式之命令模式【命令解耦】

在講解命令模式之前我們先來了解一個生活中的命令模式場景:

場景1:
醫院看病抓藥:
當你因為腎虛到醫院看醫生,醫生一番操作之後得出結論:要吃個療程【夏桑菊】、【小柴胡】(藥名純屬虛構,真的腎虛就找醫生),於是醫生開了個藥單【夏桑菊、小柴胡】,讓你拿著藥單到收費視窗;於是射射發抖的到收費視窗把藥單【夏桑菊、小柴胡】給到收費人員,收費人員不管給你看醫生的是誰,你是否得腎虛,他只要對著藥單得收費項進行收費,完成後在藥單上蓋個收費章,然後讓你拿著藥單到取藥視窗拿藥,於是你又跑到取藥視窗,將藥單【夏桑菊、小柴胡】給到工作人員,工作人員到各個藥櫃找到你要得藥給到你。

場景2:
後廚製作:
當你來到一家餐廳吃飯,你跟服務員說你要吃【韭菜、秋葵、生蠔】,於是服務員將寫在訂單紙【韭菜、秋葵、生蠔】,把它插到後廚視窗,大廚們拿到訂單紙【韭菜、秋葵、生蠔】,之後紅紅哈嘿把菜做好。
相信你很容易從上面的案例中z找到共同點,他們的工作模式。

對於場景一:

  • 醫生:他並不知道給你收費的是誰,怎麼收費,也不知道誰給你拿藥,怎麼找到藥。他就知道給你一張藥單。
  • 收費人:他並不知道是給你的藥單,他只需要按照藥單上面寫著的給你結賬,也不管你下一步要做什麼。
  • 取藥人:他並不知道給你看到醫生是誰,給你結賬的是誰,只要藥單上蓋了章,就給你拿藥。
    -你 :攜帶者藥單,到處找人辦事,你也不管收費人是怎麼給你收的,也不關心取藥人怎麼找到藥,你只需要將藥單交給他們。他們就知道怎麼做了

對於場景2:

  • 你:你不用糾結誰給你做的菜,誰給你下的單,你就提出你的需求。
  • 服務員:根據你的需求生成張訂單,然後給到後廚,不關心後廚怎麼做菜。
  • 廚師:不關心吃的人是誰,服務員是誰,只需要拿到訂單,做出菜品。

總結:大家都只關注到【單子/命令】,而不關心其他人是怎麼做的。

引用《JavaScript設計模式及與開發實踐》中的原話:
命令模式最常見的應用場景:有時候需要向某些物件傳送請求,但是並不知道請求的接收者是誰,也不知道請求的操作是什麼,此時希望有一種鬆耦合的方式來設計程式,使得請求傳送者與請求接收者能夠消除彼此之間的耦合關係。

用場景2的例子套入原話:有時候【顧客】需要向【廚師】傳送請求【吃生蠔...】,但【顧客】實際上並不知道請求【吃生蠔】是哪個廚師做的(甚至生蠔在隔壁店的廚師幫忙都有可能),也不知道【廚師】接到請求【吃生蠔】之後他會怎麼做出這道菜,此時希望有一箇中間人【服務員】幫忙處理【顧客】跟【廚師】的關係,使得【顧客】跟【廚師】沒有產生耦合關係。而【服務員】是負責將【顧客】請求命令帶給【廚師】。

場景2案例如何程式碼實現:

首先我們要理清楚這種模式涉及到的角色:
由三種角色構成:

  • 釋出者 invoker(發出命令,持有命令物件)
  • 接收者 receiver (命令處理者,不知道誰發起請求)
  • 命令 command(接收命令,分發給對應接收者處理,持有接收者)

首先我們來講解一個非常簡單的案例:

  // 案例 

  // 接受者
  class Receiver  {
    execute () {console.log('處理')};
  }
  // 命令 
  class Command {
    constructor (receiver) {
      this.receiver = receiver;
    }
    execute () {
      this.receiver.execute();
    }
  }
  // 釋出者
  class Invoker {
    constructor (command) {
      this.command = command;
    }
    invoke () {
      this.command.execute();
    };
  }
  // 執行
  let command = new Command(new Receiver());
  let invoker = new Invoker(command);
  invoker.invoke();

結合到案例2的場景當中:

  // 廚師 擁有各項才藝
  class Cook {
    makeVegetables () {
      console.log('make vegetable');
      return 'vegetable';
    }
    makeFish () {
      console.log('make fish');
      return 'fish';
    }
    execute () {
      this.makeFish();
      this.makeVegetables();
    }
  };

  // 命令物件
  class Command {
    constructor (cook) {
      this.cook = cook;
    } 
    execute () {this.cook.execute};
  }

  // 服務員 命令的釋出者
  class Waiter {
    constructor (command) {
      this.command = command;
    }
    invokeCommand () {
      this.command.execute();
    };
  }

其實在js中函式作為一等公民,能夠隨意傳遞的來講,有更加簡化程式碼的方式:

  // 案例 ------------------------------ js 簡化 -----------------------------------------

  class Receiver  {
    execute () {console.log('執行邏輯')};
  }
  class Invoker {
    invoke (receiver) {
      receiver.execute();
    };
  }
  let invoker = new Invoker();
  invoker.invoke(new Receiver());

我還是更加推薦上面的寫法,至少看起來更加清晰的結構,命令模式的使用更加易懂。

巨集命令

是一組命令的集合,通過執行巨集命令的方式,可以一次執行一組命令。

場景:如果你家裡買了很多小米的智慧家電,你希望一下班回來就自動都開啟燈,開啟空調,播放音樂...;這豈不美哉。

  // 案例 ------------------------------ 巨集命令 -----------------------------------------

  // 定義命令
  class OpenLightCommand {
    execute() {
      console.log('open light');
    }
  }
  class PlayMusicCommand {
    execute() {
      console.log('play light');
    }
  }
  class OpenAirConditioningCommand {
    execute() {
      console.log('open air conditioning');
    }
  }

  // 命令集合
  class MacroCommand {
    constructor() {
      this.commandList = [];
    }
    add(command) {
      this.commandList.push(command);
    }
    clear() {
      this.commandList = [];
    }
    execute() {
      for (let i = 0; i < this.commandList.length; i++) {
        this.commandList[i].execute();
      }
    }
  }

  class ITMan {
    constructor(command) {
      this.commamd = command;
    }
    whenGoHome() {
      this.commamd.execute();
    }
  }

  let macroCommand = new MacroCommand();
  macroCommand.add(new OpenLightCommand());
  macroCommand.add(new PlayMusicCommand());
  macroCommand.add(new OpenAirConditioningCommand());

  let itMan = new ITMan(macroCommand);
  itMan.whenGoHome();

總結:這種模式的寫法跟策略模式有很大的相似性,但目的性卻完全不同。命令模式更多的是為了解決請求方與實現方的解耦。