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();

总结:这种模式的写法跟策略模式有很大的相似性,但目的性却完全不同。命令模式更多的是为了解决请求方与实现方的解耦。

posted @ 2020-08-20 12:13  bigname22  阅读(284)  评论(0编辑  收藏  举报