使用JavaScript浅谈命令模式

什么是命令模式?

今天我去餐馆叫了服务员,我要吃这个那个,服务员拿着便条给我记录了下来,然后去厨房,把便条给厨师,厨师就知道要做什么菜了。

嗯,确实奢侈了一点,我现在所在的地方物价很高,之前点了一个菜56,还是很普同的菜。哈哈扯了一点题外话。

从上面我去餐馆点菜到服务员记录到厨师做菜,我们知道哪个厨师做的菜,是怎么做的菜,但只要有菜就行了,就能诠释什么是命令模式。

请求发送者(我)-》命令对象(服务员手中的便条)-》请求接受者(厨师)

所以命令模式就是一个请求发送者和一个请求接受者通过命令对象实现整个请求的实现。

可以看出,命令模式是一种松耦合的程序设计,使得请求的发送者和请求的接受者能够消除彼此之间的耦合关系。

关于传统的面向对象编程的命令模式的实现,在这里我不做分析,因为我是写JavaScript的啊!

假设有这么一个场景,点击上一页按钮进入上一页,点击下一页按钮进入下一页

我们看一段代码:

// 下一页
var next = document.getElementById('next');
// 上一页
var prev = document.getElementById('prev');

// 注册下一页命令
var setNextCommand = function (button, command) {
    button.onclick = function () {
        command.next();
    }
}
// 注册上一页命令
var setPrevCommand = function (button, command) {
    button.onclick = function () {
        command.prev();
    }
}
// 请求接受者
var RouteBar = {
    next: function () {
        console.log('下一页');
    },
    prev: function () {
        console.log('上一页')
    }
}
// 创建命令
var RefreshMenuBarCommand = function (receiver) {
    return {
        next: function () {
            receiver.next();
        },
        prev: function () {
            receiver.prev()
        }
    }
}
var command = RefreshMenuBarCommand(RouteBar);
setNextCommand(next, command);
setPrevCommand(prev, command);

在这里我们定义了请求接受者RouteBar,请求的发送者next和prev两个按钮,然后使用setNextCommand和setPrevC0mmand两个给请求发送者绑定命令,之后使用RefreshMenuBarCommand方法,把请求接受者的行为绑定到了一个命令对象上,并返回这个对象。

擦,你说你一个上一页和下一页有那么麻烦嘛?是的你说的没有错,在这种简单的程序中,使用设计模式确实不得劲,反而会增加代码量和使得逻辑变得复杂。所以说编程设的时候设计模式不是用的越多越好。

嗯,不要着急,我们只是热一下身,对命令模式有一个基础的认识。

来,我们继续,我们来实现一个计算的功能,不关能计算,而且还要能恢复到上一次的计算结果的功能。看看代码:

// 请求接受者
var receiver = (function () {
    var count = 0;
    return {
        calc: function (num) {
            count = num;
            console.log('现在的值是:' + count);
        },
        getValue: function () {
            return count;
        }
    }
})();

// 定义计算命令
var CalcCommand = function (receiver) {
    this.receiver = receiver;
    this.oldValue = 0;
}
CalcCommand.prototype.execute = function (num) {
    this.oldValue = this.receiver.getValue(num);
    this.receiver.calc(num);
}
CalcCommand.prototype.unCalc = function () {
    this.receiver.calc(this.oldValue);
}
var calcCommand = new CalcCommand(receiver);

calcCommand.execute(300);
calcCommand.execute(500);
calcCommand.unCalc();

这里我们就可以实现计算之后,根据命令可以恢复到上次的计算结果,测试结果如下:

 

我么第一次设置值为300,在设置值为500,那么上一次值就是300,在调用命令对象的unCalc方法后,得到了我们想要的结果。

有了这个基本的思想,我们设想一下,五子棋游戏的悔棋功能是不是可以实现了,唯一不同的是可以一直悔棋,我们这里只能回退一步,那你把每一布的命令加入到一个缓存队列,悔棋的时候依次执行这个队列里面的命令是不是就达到了悔棋的功能。嗯。。。说是这么说,我现在也还没有实践,后面有时间会实现一下,毕竟我也是五子棋的爱好者(假的)。

当我们看到一个美女跳舞的时候,我们是不是被美女婀娜多次的舞蹈所倾倒(我是被外貌倾倒,咳咳咳),但是我们知道每一个舞蹈都是按照一定的姿势来的,只有每一个姿势都会了,然后组合在一起演绎出来,就成了我们看过的舞蹈。

来来来,我们想一下每一个姿势我们是不是可以看做一个命令,当完成所有的命令之后,我们在一次性的顺序执行这些命令是不是就变成了舞蹈。

我们来实现一下这个从练习到演绎的过程:

// 定义舞蹈每一个姿势具体的表现 (请求接受者)
var frames = {
    hand: function () {
        console.log('快看,美女举起了手')
    },
    foot: function () {
        console.log('厉害,美女伸出了腿')
    },
    head: function () {
        console.log('不错,美女摇起了头')
    },
    butt: function () {
        console.log('我擦,美女翘起了屁股');
    }
}
// 依次要训练的舞蹈姿势 (命令发送者)
var commands = ['hand', 'hand', 'head', 'butt', 'foot'];

// 保存舞蹈执行的顺序 (命令的堆栈)
var commandStack = [];

// 美女们看视频了解那个命令执行那个动作 (创建命令)
var danceCommand = function (receiver, frame) {
    return function () {
        receiver[frame]();
    }
}

console.log('美女们开始练习舞蹈');
commands.forEach(function (frame) {
    var command = danceCommand(frames, frame);
    if (command) {
        command();
        commandStack.push(command);
    }
});
console.log('一个小时了,美女们开始表演舞蹈了');
var _command;
while (_command = commandStack.shift()) {
    _command();
}

测试一下:

 

 实不相瞒,这个舞蹈最后是怎样的,请自行脑补(口水流一地)。

通过这一个案例我们可以实现很多东西,比如结合canvas可以实现录制视频的功能(我想你应该你知道怎么做吧,不懂私聊我)。

好啦,see you啦啦,看舞蹈去了。 

 

posted @ 2020-03-22 23:16  只会一点前端  阅读(396)  评论(0编辑  收藏  举报