望文生义,所谓的命令模式其实就是:
发出一定的指令,然后由对象接受并且执行
需要强调一点,就是对于命令的发出者来说,他并不知道该命令是给谁,执行效果是怎样,他只管发出命令就行。听到这里感觉和发布订阅者模式有异曲同工的效果。 但事实上,他们两者应用的场景还是有比较大的区分。不仅写法上有不同,而且执行的过程也有所不同。要知道在命令模式里面执行的效果是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,如果大家有自己独到的见解,欢迎拍砖(请轻点~). 也欢迎点个推荐呗。