面试官:说说你对命令模式的理解?应用场景?

开发 前端
命令模式是最简单和优雅的模式之一,命令模式中的命令指的是一个执行某些特定事情的指令。

[[434039]]

本文转载自微信公众号「JS每日一题」,作者灰灰 。转载本文请联系JS每日一题公众号。

一、是什么

命令模式是最简单和优雅的模式之一,命令模式中的命令指的是一个执行某些特定事情的指令

该模式旨在将函数的调用、请求和操作封装成为一个单一的对象

请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令

例如在一个快餐店,用户向服务员点餐。服务员将用户的需求记录在清单上:

  • 请求者点菜:参数是菜名(我要什么菜),时间(什么时候要),该需求封装起来后,如果有变化我可以修改参数
  • 命令模式将点餐内容封装成为命令对象,命令对象就是填写的清单
  • 用户不知道接收者(厨师)是谁,也不知道厨师的炒菜方式与步骤
  • 请求者可以要求修改命令执行时间,例如晚1小时再要

二、实现

命令模式由三种角色构成:

  • 发布者 invoker(发出命令,调用命令对象,不知道如何执行与谁执行)
  • 接收者 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.execute(); 
  } 

 
const warehouse = new Receiver();       // 厨师 
const order = new Command(warehouse);   // 订单 
const client = new Invoker(order);      // 请求者 
client.invoke(); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

三、应用场景

命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时,希望用一种松耦合的方式来设计程序,使的请求发送者和请求接收者能够消除彼此之间的耦合关系

菜单

现在我们需要实现一个界面,包含很多个按钮。每个按钮有不同的功能,我们利用命令模式来完成

<button id="button1"></button> 
<button id="button2"></button> 
<button id="button3"></button> 
 
<script> 
  var button1 = document.getElementById("button1"); 
  var button2 = document.getElementById("button2"); 
  var button3 = document.getElementById("button3"); 
</script> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

然后定义一个setCommand函数,负责将按钮安装命令,可以确定的是,点击按钮会执行某个 command 命令,执行命令的动作被约定为调用 command 对象的 execute() 方法。如下:

var button1 = document.getElementById('button1'
var setCommand = function(button, conmmand) { 
  button.onclick = function() { 
    conmmand.execute() 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

点击按钮之后具体行为包括刷新菜单界面、增加子菜单和删除子菜单等,这几个功能被分布在 MenuBar 和 SubMenu 这两个对象中:

var MenuBar = { 
  refresh: function() { 
    console.log('刷新菜单目录'
  } 

var SubMenu = { 
  addfunction() { 
    console.log('增加子菜单'
  }, 
  del: function(){ 
    console.log('删除子菜单'); 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这些功能需要封装在对应的命令类中:

// 刷新菜单目录命令类 
class RefreshMenuBarCommand { 
    constructor(receiver) { 
        this.receiver = receiver; 
    } 
 
    execute() { 
        this.receiver.refresh(); 
    } 

 
// 增加子菜单命令类 
class AddSubMenuCommand { 
    constructor(receiver) { 
        this.receiver = receiver; 
    } 
 
    execute() { 
        this.receiver.refresh(); 
    } 

 
// '删除子菜单命令类 
class DelSubMenuCommand { 
    constructor(receiver) { 
        this.receiver = receiver; 
    } 
 
    execute() { 
        this.receiver.refresh(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

最后就是把命令接收者传入到 command 对象中,并且把 command 对象安装到 button 上面:

var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar); 
var addSubMenuCommand = new AddSubMenuCommand(SubMenu); 
var delSubMenuCommand = new DelSubMenuCommand(SubMenu); 
 
setCommand(button1, refreshMenuBarCommand); 
setCommand(button2, addSubMenuCommand); 
setCommand(button3, delSubMenuCommand); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

撤销

命令模式的作用不仅是封装运算块,而且可以很方便地给命令对象增加撤销操作

页面中有一个 input 文本框和一个 button 按钮,文本框中可以输入一些数字,表示小球移动后的水平位置,小球在用户点击按钮后立刻开始移动,如下:

<div 
  id="ball" 
  style="position: absolute; background: #000; width: 50px; height: 50px" 
></div> 
输入小球移动后的位置:<input id="pos" /> 
<button id="moveBtn">开始移动</button> 
<script> 
  var ball = document.getElementById("ball"); 
  var pos = document.getElementById("pos"); 
  var moveBtn = document.getElementById("moveBtn"); 
  moveBtn.onclick = function () { 
    var animate = new Animate(ball); 
    animate.start("left", pos.value, 1000, "strongEaseOut"); 
  }; 
</script> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

换成命令模式如下:

var ball = document.getElementById("ball"); 
var pos = document.getElementById("pos"); 
var moveBtn = document.getElementById("moveBtn"); 
var MoveCommand = function (receiver, pos) { 
  this.receiver = receiver; 
  this.pos = pos; 
}; 
MoveCommand.prototype.execute = function () { 
  this.receiver.start("left", this.pos, 1000, "strongEaseOut"); 
}; 
var moveCommand; 
moveBtn.onclick = function () { 
  var animate = new Animate(ball); 
  moveCommand = new MoveCommand(animate, pos.value); 
  moveCommand.execute(); 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

撤销操作的实现一般是给命令对象增加一个名为 unexecude 或者 undo的方法,在该方法里执行 execute 的反向操作

在 command.execute 方法让小球开始真正运动之前,需要先记录小球的当前位置,在 unexecude 或者 undo 操作中,再让小球回到刚刚记录下的位置,代码如下:

class MoveCommand { 
    constructor(receiver, pos) { 
        this.receiver = receiver; 
        this.pos = pos; 
        this.oldPos = null
    } 
 
    execute() { 
        this.receiver.start('left', this.pos, 1000, 'strongEaseOut'); 
        this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName];  // 记录小球开始移动前的位置 
    } 
 
    undo() { 
        this.receiver.start('left', this.oldPos, 1000, 'strongEaseOut'); // 回到小球移动前记录的位置 
    } 

 
var moveCommand; 
moveBtn.onclick = function () { 
  var animate = new Animate(ball); 
  moveCommand = new MoveCommand(animate, pos.value); moveCommand.execute(); 
}; 
cancelBtn.onclick = function () { 
  moveCommand.undo();// 撤销命令 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

现在通过命令模式轻松地实现了撤销功能。如果用普通方法调用来实现,也许需要每次都手工记录小球的运动轨迹,才能让它还原到之前的位置

而命令模式中小球的原始位置在小球开始移动前已经作为 command 对象的属性被保存起来,所以只需要再提供一个 undo 方法,并且在 undo方法中让小球会到刚刚记录的原始位置就可以

参考文献

https://www.runoob.com/design-pattern/command-pattern.html

https://juejin.cn/post/6844903673697402888

https://juejin.cn/post/6995474681813811208

 

责任编辑:武晓燕 来源: JS每日一题
相关推荐

2021-11-05 07:47:56

代理模式对象

2021-11-10 07:47:49

组合模式场景

2021-11-03 14:10:28

工厂模式场景

2021-11-11 16:37:05

模板模式方法

2021-11-22 23:50:59

责任链模式场景

2021-09-16 07:52:18

算法应用场景

2021-08-16 08:33:26

git

2021-11-04 06:58:32

策略模式面试

2021-09-28 07:12:09

测试路径

2021-09-06 10:51:27

TypeScriptJavaScript

2021-09-29 07:24:20

场景数据

2021-09-10 06:50:03

TypeScript装饰器应用

2021-10-13 18:01:33

快速排序场景

2021-10-08 09:59:32

冒泡排序场景

2021-10-09 10:25:41

排序应用场景

2021-09-08 07:49:34

TypeScript 泛型场景

2021-05-31 10:35:34

TCPWebSocket协议

2021-06-01 08:25:06

Node.jsJavaScript运行

2021-10-11 09:38:41

开源

2021-10-18 07:51:39

回溯算法面试
点赞
收藏

51CTO技术栈公众号