命令模式
望文生义,所谓的命令模式其实就是:
发出一定的指令,然后由对象接受并且执行
需要强调一点,就是对于命令的发出者来说,他并不知道该命令是给谁,执行效果是怎样,他只管发出命令就行。听到这里感觉和发布订阅者模式有异曲同工的效果。 但事实上,他们两者应用的场景还是有比较大的区分。不仅写法上有不同,而且执行的过程也有所不同。要知道在命令模式里面执行的效果是1对1,而在订阅者模式里面是1对>=1的.
你在逼逼什么?
哦,说明白一点。 就和上课一样。 老师进教室了,首先说:“上课!”. 接着,你们的monitor 会立马接上: "起立!"。 然后,你们就会异口同声的说:"老湿好~"。 没错,分析一下。 当老师说上课的时候,他并不会知道谁会说起立,比如今天班长谈恋爱去了,那副班长顶上。 而且,说完起立之后,副班长也不知道谁会说老湿好。 也就是命令的发出者,只管发出一个命令,然后你们只管执行就over了.
talk is cheap, show u code.
//事件发出者
var setCommand = function(ele,command){ //命令的绑定者
ele.onclick = function(){
command.do();
}
}
//事件的执行者
var location = (function() { //执行事件类
var ball = getEle("#ball");
var move = function(direct) {
return function() {
var style = ball.style,
dir = parseInt(style[direct]);
style[direct] = `${dir-200}px`;
}
}
var moveUp = move("top");
var moveDown = move("down");
var moveLeft = move("left");
var moveRight = move("right");
return {
moveUp,
moveDown,
moveLeft,
moveRight
}
})();
//封装命令
var MoveUp = function(exer){
this.exer = exer;
}
MoveUp.prototype.do = function(){
this.exer.moveUP();
}
var MoveDown = function(exer){
this.exer = exer;
}
MoveDown.prototype.do = function(){
this.exer.moveDown();
}
var MoveLeft = function(exer){
this.exer = exer;
}
MoveLeft.prototype.do = function(){
this.exer.moveLeft();
}
var MoveRight = function(exer){
this.exer = exer;
}
MoveRight.prototype.do = function(){
this.exer.moveRight();
}
setCommand(getEle("upBtn"), new MoveUp(location)); //给向上的button,绑定向上的执行程序
setCommand(getEle("downBtn"), new MoveDown(location)); //...
setCommand(getEle("leftBtn"), new MoveLeft(location)); //...
setCommand(getEle("rightBtn"), new MoveRight(location)); //....
可以清晰的看到,在命令模式中,触发事件(onclick)和执行程序(command.do())都是已知的。 但是这个执行的消息给谁,或者执行产生的效果是怎样的,在命令的发出者这一方都是未知的。需要注意的是,这时候的未知只限于命令的发出者而言。也就是现在命令模式将发出者和执行者给解耦开,即,可变的部分和不可变的部分分开。
上面逼逼这么多到底在说shenme...
其实一切原理都是枯燥的,实例才是王道。 来,我们来做个比较。也就是不使用命令模式,直接写上面的例子(偷个懒,只写moveUP部分).
var ele = getEle("#ball");
getELe(".moveUp").onclick = function(){
var style = this.style,
dir = parseInt(style["top"]);
style["top"] = `${dir-200}px`;
}
}
上面的代码同样能完成上面辣么辣么长的代码完成的效果,那为什么还要使用上面的写法呢?
艹~ 请问,你下面那段代码,能体现你的bigger吗? 能体现你是代码艺术家的feeling吗?能体现你的思维能力吗?
No NO No~
我们来分析下why.
首先下面那段代码可以完成上面的功能,但是万一有一天,一个名叫产经的生物和你说
"亲爱的,你能不能在加一个button,让这个球可以斜着走,可以转个圈呢? 哈哈,我相信你一定可以的。"
呵呵,你话都没说。 想当然这个锅,你必须背。好吧,那开始做吧。(用那个渣渣代码演示一遍).
function getY(x){
var k = 1.2;
return k*x;
}
getELe("#diagnoal").onclick = function(){
var style = ele.style,
x = parseInt(style['left']),
y = parseInt(style['top']),
style['left'] = `${x-200}px`;
style["top"] = `${y-getY(200)}px`;
}
}
可以想象,最后如果产经的需求不断增多,那么你在事件处理的回调会越来越复杂,比如:
"亲爱的,你斜着走都实现了,那4个方向能不能都可以走呢?"
我想这时候,你应该会懵逼了。不要紧,我们可以使用命令模式来弥补这个缺陷,因为命令模式最大的一个扩展性就是命令者和命令的执行者分开了。而且在上面面向过程的代码中,看不出什么逻辑出来,只是知道,这个click是触发什么的。 而事件回调中的代码重用性也是非常低的。
这里使用命令模式重构一遍
//其他的还是一样,这里主要将4个方向的代码重构一下
var location = (function() { //执行事件类
var ball = getEle("#ball");
var compMove = function(hori,vert) { //垂直和水平方向
var k = 1.2; //移动的斜率
var getY = function() {
return k * x;
}
return function() {
var style = ball.style,
x = parseInt(style[hori]), //水平方向上的位置
y = parseInt(style[vert]); //垂直方向上的位置
style[hori] = `${x-200}px`;
style[vert] = `${y-getY(200)}px`; //执行移动
}
};
//斜方向绑定代码
var moveLU = compMOve("left","top");
var moveRU = compMOve("right","top");
var moveLB = compMOve("left","bottom");
var moveRB = compMOve("right","bottom");
return {
moveLU
moveRU,
moveLB,
moveRB
}
})();
//封装命令
var MoveLU = function(exer) {
this.exer = exer;
}
MoveLU.prototype.do = function() {
this.exer.moveLU();
}
setCommand(getEle("leftUpBtn"), new MoveLU(location)); //给向上的button,绑定向上的执行程序
可以看出来,虽然代码多,但是至少我们将改动的地方降到最低了。
setCommand这个不变,变的只是绑定click的对象和执行者。 这样可以清楚的说明命令模式的优势到底在哪里。
当然,我们还可以做一个优化,要知道,js是一门函数至上的语言,因为函数可以像参数一样被传来传去,所以可以这样改写命令的绑定者.
var setCommand = function(ele,fn){
ele.onclick = function(){
fn();
}
}
setCommand(getEle("leftUpBtn",()=>{location.moveLU()}))); //给向上的button,绑定向上的执行程序
这样就可以省去中间一大堆的事件修饰,从而将函数直接暴露使用。推荐这样写法,因为这个才是js的真正实力。
要知道一个模式的精华不是看他能怎么用,而是要看你怎么用他。
命令模式实现缓存效果
其实,缓存并不是什么高上大的东西,就是在函数里名,有一个变量来保存你的结果,而你可以遍历这个结果.
function fb(num) {
if (num <= 1) {
return 1;
}
return num * fb(--num)
}
//缓存代理出场了
var cProxy = (function() {
var cache = {};
return function(num) {
if (cache[num]) {
console.log(`this is cache ${cache[num]}`);
return cache[num];
}
return cache[num] = fb(num);
}
})();
//测试
console.log(cProxy(4)); //24
cProxy(4); //"this is cache 24"
上面是我以前写代理缓存的例子。 里面有个叫cache的东西,就是来保存你的结果(放在内存中),以备下次使用。
而命令模式的缓存有个极大的用途就是一个 撤销和重做的效果.
在上面的例子中可以保留每一个节点小球的位置(简单起见,还是以最初的上下左右为基准吧)
由于代码过长,我放在fiddle里面(里面代码可能会和上面有很大出入,但是如果理解了上面的说法的话,我相信理解起来肯定很快的)。有兴趣可以看看。是个实例demo哦。 :)
撤销实例
特此说明,由于使用原生写的里面会有写hacks,如果大家有自己独到的见解,欢迎拍砖(请轻点~). 也欢迎点个推荐呗。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。