设计模式——命令模式
有些时候我们需要像某些对象发送请求,但还没有确定的接受者和响应操作。就像导演拍戏一样,只要一声action的请求,不同的工作人员就会根据初始的职责来进行各自的响应操作。将请求者和接收者完全解耦,将响应方法封装在接收者内通过命令激活,这就是命令模式。
面向对象的命令模式
现在我们来实现一个遥控器控制电视开关的例子。设计模式的核心总是封装变化,我们知道按下按钮这个命令是不会变的,所以我们将接收者和其相应操作封装为一个命令对象。为了读者都能理解我们不采用ES6的写法实现面向对象。
let TV = {
open: function() {
console.log('open');
}
}
let OpenTVCommand = function(receiver) {
this.receiver;
}
OpenTVCommand.prototype.execute = function() {
this.receiver.open();
}
现在我们就能通过这个构造函数生成打开电视的命令对象了,接下来只要设置按钮激活命令的函数即可。
let setCommand = function(button, command) {
button.onclick = function() {
command.execute();
}
}
以上就实现了一个命令模式,虽然简单,但是思想上已经解耦了请求者和发送者。
JS中的命令模式
由于命令模式的关键是将执行方法和接收对象封装并传递,JS作为将函数作为一等公民的语言,本身就可以传递普通函数,所以我们可以通过闭包来封装执行方法,直接通过回调函数实现命令的设置。
let OpenTVCommand = function(receiver) {
return {
execute: function() {
receiver.open();
}
}
};
let setCommand = function(button, command) {
button.onclick = function() {
command.execute();
}
}
命令模式的核心应用 —— 控制时间队列
由于命令请求和响应的解耦,可以在响应的执行时机上有更多的自由度。举个例子,未使用命令模式之前,有一个播放视频请求会执行播放5分钟的操作。通过命令模式的解耦,可以拆分为5个播放每1分钟的命令。通过对这5个命令的组合就可以实现重播,乱序播放,延迟播放等功能。实际上命令模式的好处就在于将一段连续的事件按照自己的需要进行拆分后进行时间上的再重组。大多数应用场景——录像,撤销,重播,宏命令等其实都是这个原理。下面以一个实际场景举例。
假设我们现在要实现一个从a到d的操作,最简单的一个函数实现如下:
let a2d = function() {
console.log('a to d');
}
这基本就没什么变化了,就是一段强耦合的逻辑实现。而a到d这件事在时间上并不是一个点,而是一条线。那么我们通过命令模式解耦为到b,到c,到d三个命令如下:
let b = {
execute: function() {
console.log('to b');
}
};
let c = {
execute: function() {
console.log('to c');
}
};
let d = {
execute: function() {
console.log('to d');
}
};
解耦后我们就不仅能实现a到d这一件事了,还能d到a,重复的去b,延迟一段时间再去d各种方式。说到底,就是对事件在时间流的控制。下面是个宏命令操作的一个实现:
let MarcoCommand = function() {
return {
commandList: [],
add: function(command) {
this.commandList.push(command);
return this;
},
execute: function() {
for(let i = 0,command; command = this.commandList[i++];) {
command.execute();
}
}
};
let marcoCommand = MarcoCommand();
marcoCommand.add(b).add(c).add(d).execute(); // 顺序执行
小结
由于闭包的特性,我们可以说在js中经常不自觉的就会用到命令模式。在正式了解其原理后,相信今后能实现更加有目的性的命令模式。
-- EOF --