组合模式就是用小的叶对象来构建更大的对象,而这些小的叶对象本身也许是由更小的“孙对象”构成的。
1. 回顾宏命令
//叶对象
var closeDoorCommand = {
execute: function () {
console.log('关门')
},
}
//叶对象
var openPcCommand = {
execute: function () {
console.log('开电脑')
},
}
//叶对象
var openQQCommand = {
execute: function () {
console.log('登录 QQ')
},
}
//组合对象
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command)
},
execute: function () {
for (var i = 0, command; (command = this.commandsList[i++]); ) {
command.execute()
}
},
}
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()
2. 组合的用途
组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,下面分别说明
表示树形结构。通过回顾上面的例子,我们很容易找到组合模式的一个优点:提供了一种遍历树形结构的方案,通过调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、打开电脑、登录 QQ 这几件事情。组合模式可以非常方便地描述对象部分-整体层次结构。利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。
3. 更加强大的宏命令
目前的万能遥控器,包含了关门、开电脑、登录 QQ 这 3 个命令。现在我们需要一个“超级万能遥控器”,可以控制家里所有的电器,这个遥控器拥有以下功能:
-
打开空调
-
打开电视和音响
-
关门、开电脑、登录 QQ
首先在节点中放置一个按钮 button 来表示这个超级万能遥控器,超级万能遥控器上安装了一个宏命令,当执行这个宏命令时,会依次遍历执行它所包含的子命令,代码如下:
<html>
<body>
<button id="button">按我</button>
</body>
<script>
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command)
},
execute: function () {
for (var i = 0, command; (command = this.commandsList[i++]); ) {
command.execute()
}
},
}
}
var openAcCommand = {
execute: function () {
console.log('打开空调')
},
}
/**********家里的电视和音响是连接在一起的,所以可以用一个宏命令来组合打开电视和打开音响的命令
*********/
var openTvCommand = {
execute: function () {
console.log('打开电视')
},
}
var openSoundCommand = {
execute: function () {
console.log('打开音响')
},
}
var macroCommand1 = MacroCommand()
macroCommand1.add(openTvCommand)
macroCommand1.add(openSoundCommand)
/*********关门、打开电脑和打登录 QQ 的命令****************/
var closeDoorCommand = {
execute: function () {
console.log('关门')
},
}
var openPcCommand = {
execute: function () {
console.log('开电脑')
},
}
var openQQCommand = {
execute: function () {
console.log('登录 QQ')
},
}
var macroCommand2 = MacroCommand()
macroCommand2.add(closeDoorCommand)
macroCommand2.add(openPcCommand)
macroCommand2.add(openQQCommand)
/*********现在把所有的命令组合成一个“超级命令”**********/
var macroCommand = MacroCommand()
macroCommand.add(openAcCommand)
macroCommand.add(macroCommand1)
macroCommand.add(macroCommand2)
/*********最后给遥控器绑定“超级命令”**********/
var setCommand = (function (command) {
document.getElementById('button').onclick = function () {
command.execute()
}
})(macroCommand)
</script>
</html>
4. 注意点
组合模式不是父子关系。组合模式是一种 HAS-A(聚合)的关系,而不是 IS-A。组合对象包含一组叶对象,但 Leaf并不是 Composite 的子类。组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。对叶对象操作的一致性。组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。- 双向的映射关系
- 用职责链模式提高组合模式性能
5. 何时使用组合模式
表示对象的部分-整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放封闭原则。客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆 if、else 语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情,这是组合模式最重要的能力。