命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。
命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和接收者能够消除彼此之间的耦合关系。
拿下馆子吃饭举例,我们通常去了小炒店吃饭会先点菜,当我们选好想吃的菜和饮品的时候,我们需要向厨师发送请求,让厨师为我们做我们想吃的菜,但是我们完全不需要这些厨师的名字或者联系方式,也不知道这个厨师会如何去炒我们要吃的菜。而命令模式就是将我们要吃菜的这个请求封装成command对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像我们想吃的菜的订单会从服务员那儿传到厨师手中。这样一来我们不需要知道厨师的名字,厨师也不需要知道是谁要吃这个菜,从而解开了顾客和厨师之间的耦合关系。
另外相对于过程化的请求调用,command对象拥有更长的生命周期。对象的生命周期是跟初始请求无关的,因为这个请求已经被封装在了command对象的方法中,成为了这个对象的行为。我们可以在程序运行的任意时刻去调用这个方法,就像厨师可以在客人预订1个小时之后才帮顾客炒菜,相当于程序在1个小时之后菜开始执行command对象的方法。
JavaScript中的命令模式
JavaScript作为将函数作为一等对象的语言,和策略模式一样,命令模式也早已融入到了JavaScript语言中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。
我们用js的命令模式实现一下上面顾客点餐的例子:
const kitchen = {
cookers: [{
name: '小明',
isCooking: false,
orderList: []
},
{
name: 'dasima',
isCooking: false,
orderList: []
}
]
}
const customer = {
placeAnOrder: function () {
const orderList = ['鱼香肉丝', '小炒黄牛肉']
return orderList
}
}
const waiter = {
orderList: [],
getOrderList: function (orderList) {
this.orderList = orderList
},
divideOrder: function () {
for (cooker of kitchen.cookers) {
if (!cooker.isCooking) {
cooker.orderList = this.orderList
cooker.isCooking = true
break
}
}
}
}
waiter.getOrderList((customer.placeAnOrder()))
waiter.divideOrder()
console.log(kitchen)
从上面的例子可以看出来,顾客下单的菜即orderList通过服务员传递到厨师的手中,顾客并不需要关注菜是谁做的,而厨师也不需要菜是谁点的,完美实现了双方的解耦。
命令队列
在上面下馆子的例子中,如果是中午饭点时间段小炒店里通常是会有许多客人的,这个时候订单数量会过多而厨师的人手不够,则可以让这些订单进行排队处理。第一个订单完成之后,再开始执行第二个订单相关的操作。
比如玩英雄联盟这类moba类型或者格斗类型游戏的时候,我们每下达一个命令,那么被我们操作的角色就会做出相应命令的动作。通常情况下这些动作都是有动画的,如果我们很快速的按下一系列按键形成角色的连招,第一个技能的动画还没结束,第二个技能的动画就开始了,这通常不是我们想要的,这个时候命令队列的重要性就体现出来了。
我们可以把每一个命令都封装成一个对象,将它们排成一个角色的命令队列,让角色依次去执行队列里的命令,注意这里是队列要先进先出。每当角色完成一个动作就通知队列,从队列之中取出下一个动作去执行。
宏命令
宏命令是一组命令的集合,通过执行宏命令的方式可以一次执行一批命令。其实如果以前写过JSP或者写过原生的小伙伴都应该接触过这种,比如当我们请求了ajax请求获取数据之后,我们需要把数据展示到不同的地方,每展示一个地方的数据其实都可以看做是一个命令,那么整个回调函数就可以看做是一个宏命令。又或者现在的智能家居,我们可以设置当我们回到家打开门的时候,会依次打开客厅的灯和空调顺带再把窗户给关上,其实这也是一个宏命令例子。
总结
JavaScript可以用高阶函数或者闭包的特性非常方便的实现命令模式,甚至可以说命令模式在JavaScript中就是一种隐形的模式。