组合模式,从回家后开始

807 阅读3分钟

搬了一天砖后,拖着疲惫的身体回家,打开家门...

image.png

从图中可以看出,开烤箱后的组合行为是烤面包和烤肠。

进厨房后的组合行为,是开烤箱后的组合行为、煮鸡蛋和煮咖啡共同组成。

回家后的行为,是由关门、开灯、进厨房的组合行为和打开电视共同组成。

这样,就可以组成一颗行为树,但是,可以发现,蓝色区域是多个行为的组合,而非行为的自身执行,真正的执行由具体的行为动作完成。

我们先定义一个可以返回组合行为对象添加和执行的函数:

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()后的执行结果是:

image.png

深度遍历流程图如下:

image.png

总结

组合模式,在执行根组合对象、节点组合对象和叶子对象时都是execute,也就说不管从哪里开始,都可以执行execute,这让组合模式的使用变得简单。访问者几乎没有上手成本。简单得像摁个按钮,只需要知道开灯、开电视、开烤箱、咖啡机等设备的开关一样。

可以通过afterEnterKitchenCommand.execute()进行进入厨房后的组合行为执行。

可以通过afterOpenOvenCommand.execute()进行打开烤箱后的组合行为执行。

也可以通过turnOnLightCommand.execute()turnOnTvCommand.execute()进行叶子对象的执行,分别执行了开灯和打开电视的命令。

最后抽象组合模式的树形结构:

image.png

组合模式表示的是局部和整体的关系,局部和整体的关系往往是通过树来描述的。树我们知道由根、枝干和叶子构成,同样,抽象的树也是由根节点,节点和叶子节点构成。