js设计模式——命令模式

一、应用场景

​ 命令模式主要用于这样的场景:假设我们有时候需要向一些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。这时候我们就需要采用一种吗松耦合的方式来设计。既能够简历请求者与接收者之间的关系又能消除彼此之间的耦合。

​ 比如在下面的示例场景中,我们有几个button,假设现在在一个大型的项目中,我们有程序员在设计和布局按钮,同时又有另外的程序员来设计这些按钮的功能。在这个场景中,负责布局的程序员并不知道之后这些按钮具体会是什么样的功能。那么他应该怎样来设计这个按钮的功能来更好的和负责设计按钮功能的程序员进行很好的对接呢?这时候就需要用到我们的命令模式。

二、一个简单的实例

​ 根据上面的需求场景我们设置下面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<body>
<button id="button1">点击按钮1</button>
<button id="button2">点击按钮2</button>
<button id="button3">点击按钮3</button>
</body>
<script>
var button1 = document.getElementById('button1')
var button2 = document.getElementById('button2')
var button3 = document.getElementById('button3')

var setCommand = function(button,commend){
button.onclick = function(){
commend.execute()
}
}
var MenuBar = {
refresh:function(){
console.log('刷新菜单目录')
}
}
var SubMenu = {
add:function(){
console.log('增加菜单目录')
},
del:function(){
console.log('删除菜单目录')
}
}
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();
};

var refreshMenuBarCommand = new RefreshMenuBarCommand( MenuBar );
var addSubMenuCommand = new AddSubMenuCommand( SubMenu );
var delSubMenuCommand = new DelSubMenuCommand( SubMenu );
setCommand( button1, refreshMenuBarCommand );
setCommand( button2, addSubMenuCommand );
setCommand( button3, delSubMenuCommand );
</script>
</html>

​ 在上面的这个实例中,假设我们有三个按钮,这三个按钮设计好之后,通过setCommand将具体的按钮和其对应的指令绑定在一起。这里,按钮也并不知道将要执行的是什么样的指令。在下面就是对应的设计功能的程序员的代码。首先建立了几个指令的对象分别对应不同的行为,接下来建立指令对象将这些指令和对象绑定在一起。然后利用之前写好的setCommand函数将指令对象和我们的具体按钮对应起来。

​ 以上是一种面向对象的设计方案,主要采用的是通过对象的绑定方式。我们同样还可以用闭包的方式来实现这个过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var setCommand = function(button,commend){
button.onclick = function(){
commend();
}
}
var SubMenu = {
add:function(){
console.log('刷新菜单目录')
}
}
var RefreshMenuBarCommand = function(receiver){
return function(){
receiver.add()
}
}
var refreshMenuBarCommand = RefreshMenuBarCommand(SubMenu)
setCommand(button1,refreshMenuBarCommand);

二、撤销命令

​ 有执行命令自然也就有撤回命令。在我们的程序中,假设有一个场景是我们在做一个棋盘类游戏的程序,这时候需要进行悔棋操作。这时候我们就需要撤销之前的几步命令。通常的做法是我们首先定义一个存储操作命令的数组,在执行命令的对象里面每次执行的时候都将对应的操作存放在这个数组当中。同时我们的在我们的执行命令的对象当中加上一个方法用来在进行命令的撤回。只要的工作就是从我们的存放操作命令的数组中取出这样的元素并执行逆过程然后删除。

​ 然而有的命令在执行完毕之后并不容易进行回退操作,这时候我们就可以采用历史栈的重新执行的方式进行。我们可以考虑建立一个历史操作的堆栈,并在每次需要执行回退的时候重投开始执行到回退的地方,这样就能就解决撤销得到指令不可逆的问题。比如我们在进行一些游戏操作的时候,需要回看操作录像,这个时候我们就可以利用这个操作的堆栈来实现操作回放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<body>
<button id="button1">点击进行回放</button>
</body>
<script>
var bnt1 = document.getElementById('button1');
var options = {
"up":()=>{
console.log("up")
},
"down":()=>{
console.log("down")
},
"left":()=>{
console.log("left")
},
"right":()=>{
console.log("right")
}
}
var makeCommand = function(reciver,state){
return function(){
reciver[state]();
}
}
//这个对象用来决定按下哪个键执行的对应的操作
var commands = {
"119":"up",
"97":"left",
"115":"down",
"100":"right"
}
//执行命令的调用栈
var commandSatck = [];
document.onkeypress = (ev)=>{
//console.log(ev.keyCode)
var keycode = ev.keyCode
var command = makeCommand(options,commands[keycode])
if(command){
command()
commandSatck.push(command) //将执行的指令压入堆栈中
}
}
document.getElementById('button1').onclick = function(){
var command;
while(command = commandSatck.shift()){
command();
}
}
</script>

四、命令队列

​ 有的命令执行的速度会比较慢,这时候如果用户当前进行的命令还没有执行完,又再次发出了下一个命令,这时候新来的命令可能会将之前的命令打断。而此时实际上用户希望的是命令能够按照发布的顺序依次执行。这时候我们就需要用到命令队列来实现命令的顺序执行,同时在一个命令执行完毕之后,下一个命令能够及时连续执行。采用的方法一种是我们可以用订阅——观察者模式来实现,还有一种是我们可以用我们可以利用回调函数的方式来实现。具体操作不再展示。

五、宏命令

​ 宏命令是对一系列的命令的一个集合。代表我们需要在发出一个命令的时候希望有多条命令一次被执行。比如我们在打开某个软件的时候,当我们点击登录按钮,他会首先检查我们当前的网络环境是否安全,然后检查用户名密码是否正确,之后会检查用户是否在其他地方登录,最后进行登录的相关初始化操作。于是以上的这一系列的操作我们可以定义在同一个宏命令中去执行,具体的方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let commend1 = {
excute:()=>{
console.log('commed1')
}
}
let commend2 = {
excute:()=>{
console.log('commed2')
}
}
let commend3 = {
excute:()=>{
console.log('commed3')
}
}
var MacroCommend = function(){
return {
commedList:[],
add:function(commend){
this.commedList.push(commend)
},
execute:function(){
this.commedList.forEach(element => {
element.excute()
});
}
}
}
var mc = MacroCommend();
mc.add(commend1)
mc.add(commend2)
mc.add(commend3)
mc.execute()
0%