搬了一天砖后,拖着疲惫的身体回家,打开家门...
从图中可以看出,开烤箱后的组合行为是烤面包和烤肠。
进厨房后的组合行为,是开烤箱后的组合行为、煮鸡蛋和煮咖啡共同组成。
回家后的行为,是由关门、开灯、进厨房的组合行为和打开电视共同组成。
这样,就可以组成一颗行为树,但是,可以发现,蓝色区域是多个行为的组合,而非行为的自身执行,真正的执行由具体的行为动作完成。
我们先定义一个可以返回组合行为对象添加和执行的函数:
var JointCommand = function () {
return {
commandList: [],
add: function (command) {
this.commandList.push(command)
},
execute: function () {
for (let i = 0, command; command = this.commandList[i++];) {
command.execute();
}
}
}
}
JointCommand执行后返回的对象表示组合对象,commandList表示命令或者行为的组合,add用来为组合命令添加命令,execute表示组合对象的执行,本质上是调用组合命令列表中的command.execute()。
其中,command也可能是组合对象,执行组合对象的时候,该列表层后续的command暂时不执行。会以深度遍历的方式,先执行组合对象的列表命令。依次类推,最终的叶子对象执行完后将执行权交给父级,层层向上,最终会完成整棵树的命令执行。
下面我们先从整棵树的叶子对象开始进行分析。
1、开烤箱后的行为
// 烤面包和烤肠
var cookieToattCommand = {
execute: function () {
console.log('烤面包')
}
}
var roastSausageCommand = {
execute: function () {
console.log('烤香肠')
}
}
// 开烤箱后的组合行为
afterOpenOvenCommand = JointCommand();
afterOpenOvenCommand.add(cookieToattCommand);
afterOpenOvenCommand.add(roastSausageCommand);
定义烤面包和烤肠的单个对象,包含execute方法。
再执行JointCommand返回打开烤箱组合对象。
最后通过afterOpenOvenCommand.add(cookieToattCommand)和afterOpenOvenCommand.add(roastSausageCommand)的方式为打开烤箱后的组合对象添加烤面包和烤香肠命令。
2、进入厨房后的行为
var boiledEggCommand = {
execute: function () {
console.log('煮鸡蛋')
}
}
var makeCoffeeCommand = {
execute: function () {
console.log('煮咖啡')
}
}
// 进入厨房后的组合行为
afterEnterKitchenCommand = JointCommand();
afterEnterKitchenCommand.add(afterOpenOvenCommand);
afterEnterKitchenCommand.add(boiledEggCommand);
afterEnterKitchenCommand.add(makeCoffeeCommand);
定义煮鸡蛋和煮咖啡的单个对象,包含execute方法。
再执行JointCommand返回进入厨房组合对象。
和打开烤箱不同的地方在于,通过afterEnterKitchenCommand.add(afterOpenOvenCommand)的方式为commandList添加的是打开烤箱后的组合对象。
最后通过afterEnterKitchenCommand.add(boiledEggCommand)和afterEnterKitchenCommand.add(makeCoffeeCommand)的方式为进入厨房后的组合对象添加煮鸡蛋和煮咖啡命令。
3、回家后的行为
var closeDoorCommand = {
execute: function () {
console.log('关门')
}
}
var turnOnLightCommand = {
execute: function () {
console.log('开灯')
}
}
var turnOnTvCommand = {
execute: function () {
console.log('打开电视')
}
}
// 回家后的组合行为
afterGoHomeCommand = JointCommand();
afterGoHomeCommand.add(closeDoorCommand);
afterGoHomeCommand.add(turnOnLightCommand);
afterGoHomeCommand.add(afterEnterKitchenCommand);
afterGoHomeCommand.add(turnOnTvCommand);
定义关门、开灯和打开电视的单个对象,包含execute方法。
再执行JointCommand返回回家后的组合对象。
最后通过afterGoHomeCommand.add(closeDoorCommand)、afterGoHomeCommand.add(turnOnLightCommand)、afterGoHomeCommand.add(afterEnterKitchenCommand)和afterEnterKitchenCommand.add(turnOnTvCommand)的方式为回家后的组合对象添加关门、开灯、进入厨房组合对象和打开电视。
这里需要注意,进入厨房后的组合对象已经是一棵树了,是回家后组合对象的子树。
当执行afterGoHomeCommand.execute()后的执行结果是:
深度遍历流程图如下:
总结
组合模式,在执行根组合对象、节点组合对象和叶子对象时都是execute,也就说不管从哪里开始,都可以执行execute,这让组合模式的使用变得简单。访问者几乎没有上手成本。简单得像摁个按钮,只需要知道开灯、开电视、开烤箱、咖啡机等设备的开关一样。
可以通过afterEnterKitchenCommand.execute()进行进入厨房后的组合行为执行。
可以通过afterOpenOvenCommand.execute()进行打开烤箱后的组合行为执行。
也可以通过turnOnLightCommand.execute()和turnOnTvCommand.execute()进行叶子对象的执行,分别执行了开灯和打开电视的命令。
最后抽象组合模式的树形结构:
组合模式表示的是局部和整体的关系,局部和整体的关系往往是通过树来描述的。树我们知道由根、枝干和叶子构成,同样,抽象的树也是由根节点,节点和叶子节点构成。