Javascript命令模式

Javascript命令模式

  • [1 什么是命令模式](#1 什么是命令模式)
  • [2 命令模式的例子---菜单程序](#2 命令模式的例子—菜单程序)
  • [3 JavaScript 中的命令模式](#3 JavaScript 中的命令模式)
  • [4 撤销命令](#4 撤销命令)
  • [5 宏命令](#5 宏命令)

1 什么是命令模式

在一个餐厅中,当客人现场点餐或者打电话订餐时,老板会把客人的需求写在清单上,厨师会按照清单的顺序给客人炒菜,有时餐厅还可以满足定时点餐,比如x时间之后再上菜,如果客人需要撤销订单,可以直接打电话给餐厅。

这些记录着订餐信息的清单 ,就是命令模式中的命令对象

命令模式允许将功能请求封装为独立的对象,并根据需要将其排队、记录、撤销或重做。该模式的核心是将请求与其调用者分离,从而使命令对象独立于发送者和接收者。

在命令模式中,有4个主要角色:命令对象、客户端、接收者和调用者。命令对象封装了一个特定的功能请求,而客户端创建命令对象并将其传递给调用者。调用者是负责执行命令的对象,并将其发送给接收者执行所需的操作。

命令模式在Javascript中有很多应用场景,例如撤销和重做功能、动态添加命令、撤销命令、实现编辑器、处理用户交互等。

对于订餐来说,客人需要向厨师发送请求,但是并不关心厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。命令模式把客人订餐的请求封装成command对象,这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。

2 命令模式的例子---菜单程序

假设我们封装一个按钮组的组件,每个按钮都有自己的click事件,对于封装这个组件的人来说,并不关心这个按钮点击之后接收者是什么对象,也不知道接收者究竟会做什么,这时我们可以借助命令对象的帮助,以便解开按钮和负责具体行为对象之间的耦合,首先是按钮组绘制:

html 复制代码
<button id="button1">点击按钮1</button>
<button id="button2">点击按钮2</button>
<button id="button3">点击按钮3</button>
<script>
  var button1 = document.getElementById("button1");
  var button2 = document.getElementById("button2");
  var button3 = document.getElementById("button3");
</script>

接下来定义setCommand函数,setCommand函数负责往按钮上面安装命令。可以肯定的是,点击按钮会执行某个command命令,执行命令的动作被约定为调用command对象的execute()方法。

javascript 复制代码
var setCommand = function (button, command) {
  button.onclick = function () {
    command.execute();
  };
};

最后是按钮的点击事件,点击按钮后分别由刷新菜单界面、增加子菜单和删除子菜单这几个功能,这几个功能被分布在MenuBarSubMenu这两个对象中:

javascript 复制代码
var MenuBar = {
  refresh: function () {
    console.log("刷新菜单");
  },
};

var SubMenu = {
  add: function () {
    console.log("添加子菜单");
  },
  del: function () {
    console.log("删除子菜单");
  },
};

我们要先把这些行为都封装在命令类中:

javascript 复制代码
var RefreshMenuBarCommand = function (receiver) {
  this.receiver = receiver;
};
RefreshMenuBarCommand.prototype.execute = function () {
  this.receiver.refresh();
};

var AddSubMenuCommand = function (receiver) {
  this.receiver = receiver;
};
AddSubMenuCommand.prototype.execute = function () {
  this.receiver.add();
};

var DelSubMenuCommand = function (receiver) {
  this.receiver = receiver;
};
DelSubMenuCommand.prototype.execute = function () {
  this.receiver.del();
};

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

javascript 复制代码
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);

setCommand(button1, refreshMenuBarCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);

以上只是一个很简单的命令模式示例,但从中可以看到我们是如何把请求发送者和请求接收

者解耦开的。

3 JavaScript 中的命令模式

也许我们会感到很奇怪,所谓的命令模式,看起来就是给对象的某个方法取了execute的名字。引入command对象和receiver这两个无中生有的角色无非是把简单的事情复杂化了,即使不用什么模式,用下面寥寥几行代码就可以实现相同的功能:

javascript 复制代码
var bindClick = function (button, func) {
  button.onclick = func;
};

var MenuBar = {
  refresh: function () {
    console.log("刷新菜单界面");
  },
};
var SubMenu = {
  add: function () {
    console.log("增加子菜单");
  },
  del: function () {
    console.log("删除子菜单");
  },
};

bindClick(button1, MenuBar.refresh);
bindClick(button2, SubMenu.add);
bindClick(button3, SubMenu.del);

这种说法是正确的,上面的示例代码是模拟传统面向对象语言的命令模式实现。命令模式将过程式的请求调用封装在command对象的execute方法里,通过封装方法调用,我们可以把运算块包装成形,command对象可以被四处传递,所以在调用命令的时候,客户不需要关心事情是如何进行的。

跟策略模式一样,命令模式也早已融入到了JavaScript语言之中。运算块不一定要封装在command.execute方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求"接收者",那也未必使用面向对象的方式,闭包可以完成同样的功能。

4 撤销命令

下面利用策略模式中Animate类编写一个动画,Animate类指路:JavaScript策略模式

这个动画的表现是让页面上的正方形移动到水平方向的某个位置。现在页面中有一个input文本框和一个button按钮,文本框中可以输入一些数字,表示正方形移动后的水平位置,正方形在用户点击按钮后立刻开始移动,代码如下:

html 复制代码
<div id="ball" class="ball"></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>
css 复制代码
.ball {
  position: absolute;
  background-color: pink;
  width: 50px;
  height: 50px;
}

如果文本框输入200,然后点击moveBtn按钮,可以看到正方形顺利地移动到水平方向200px的位置。现在我们需要一个方法让它还原到开始移动之前的位置,在页面上设计一个撤销按钮,点击撤销按钮之后,小球便能回到上一次的位置。

在给页面中增加撤销按钮之前,先把目前的代码改为用命令模式实现:

javascript 复制代码
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();
};

接下来增加撤销按钮:

html 复制代码
<div id="ball" class="ball"></div>
请输入移动后的位置:<input id="pos" />
<button id="moveBtn">开始移动</button>
<button id="cancelBtn">撤销</button>

撤销操作的实现一般是给命令对象增加一个名为unexecude或者undo的方法,在该方法里执行execute的反向操作。在command.execute方法让正方形开始真正运动之前,我们需要先记录正方形的当前位置,在unexecude或者undo操作中,再让它回到刚刚记录下的位置,代码如下:

javascript 复制代码
var ball = document.getElementById("ball");
var pos = document.getElementById("pos");
var moveBtn = document.getElementById("moveBtn");
var cancelBtn = document.getElementById("cancelBtn");

var MoveCommand = function (receiver, pos) {
  this.receiver = receiver;
  this.pos = pos;
  this.oldPos = null;
};
MoveCommand.prototype.execute = function () {
  this.receiver.start("left", this.pos, 1000, "strongEaseOut");
  // 记录小球开始移动前的位置
  this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName];
};
MoveCommand.prototype.undo = function () {
  // 回到小球移动前记录的位置
  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(); // 撤销命令
};

现在通过命令模式轻松地实现了撤销功能。如果用普通的方法调用来实现,也许需要每次都手工记录小球的运动轨迹,才能让它还原到之前的位置。而命令模式中小球的原始位置在小球开始移动前已经作为command对象的属性被保存起来,所以只需要再提供一个undo方法,并且在undo方法中让小球回到刚刚记录的原始位置就可以了。

5 宏命令

宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。

比如说,家里有一个万能遥控器,每天回家的时候,只要按一个特别的按钮,它就会帮我们关上房间门,顺便打开电脑并登录游戏。

下面我们逐步创建一个宏命令,首先,我们依然要创建好各种Command

javascript 复制代码
var closeDoorCommand = {
  execute: function () {
    console.log("关门");
  },
};
var openPcCommand = {
  execute: function () {
    console.log("开电脑");
  },
};
var openGameCommand = {
  execute: function () {
    console.log("打开游戏");
  },
};

接下来定义宏命令MacroCommandmacroCommand.add方法表示把子命令添加进宏命令对象,当调用宏命令对象的execute方法时,会迭代这一组子命令对象,并且依次执行它们的execute方法:

javascript 复制代码
var MacroCommand = function () {
  return {
    commandsList: [],
    add: function (command) {
      this.commandsList.push(command);
    },
    execute: function () {
      for (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute();
      }
    },
  };
};

var macroCommand = MacroCommand();

macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();
相关推荐
梦境之冢1 分钟前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
筑基.3 分钟前
basic_ios及其衍生库(附 GCC libstdc++源代码)
开发语言·c++
racerun4 分钟前
vue VueResource & axios
前端·javascript·vue.js
雨颜纸伞(hzs)18 分钟前
C语言介绍
c语言·开发语言·软件工程
J总裁的小芒果20 分钟前
THREE.js 入门(六) 纹理、uv坐标
开发语言·javascript·uv
m0_5485147721 分钟前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
浮游本尊29 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
新中地GIS开发老师37 分钟前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
坊钰1 小时前
【Java 数据结构】移除链表元素
java·开发语言·数据结构·学习·链表
chenziang11 小时前
leetcode hot100 LRU缓存
java·开发语言