我们先回顾下上一节中的宏命令。
var closeDoorCommand = {
execute: function(){
console.log('close door');
}
}
var openPCCommand = {
execute: function() {
console.log('open pc');
}
}
var openQQCommand = {
execute: function() {
console.log('open qq');
}
}
var MacroCommand = function() {
return {
commandsList: [],
add: function(command) {
this.commandsList.push(command);
},
execute: function(){
for(var i = 0; i< this.commandsList.length; i++) {
this.commandsList[i].execute();
}
}
}
}
var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPCCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();
其中marcoCommand被称为组合对象,closeDoorCommand、openPcCommand、openQQCommand 都是叶对象。在macroCommand的execute方法里,并不执行真正的操作,而是遍历它所包含的叶对象,把真正的execute请求委托给这些叶对象。
组合模式的定义:
组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。除了表示树形结构之外,另外一个好处是通过对象的多态性的表现,使得用户对单个对象和组合对象的使用具有一致性
组合模式的用途:
- 表示树形结构。我们看到上面的例子里面,组合对象的execute会递归调用组合对象下面的叶对象的execute方法,组合模式可以很方便的描述对象的部分-整体层次结构。
- 利用对象的多态性,统一对待组合对象和单个对象。在组合模式里面,用户不关心对象是组合对象还是单个对象,比如用户添加命令的时候,不关心是宏命令还是普通的子命令,他都可以接受。
抽象类在组合模式中的作用
我们上面提到组合模式最大的优点在于一致的对待组合对象和基本对象,客户不需要知道当前处理的是宏命令还是普通命令,只要它是个命令,并且有execute方法,这个命令就可以添加到树中。这种透明性在静态语言中显示的更为明显,我们看个例子:
public abstract class Component {
// add
public void add (Component child) {};
// remove
public void remove(Component child) {};
}
public class Composite extends Component {
// add
public void add (Component child) {};
// remove
public void remove(Component child) {};
}
public class Leaf extends Compnent {
// add
public void add (Component child) {
throw new UnsupportedOperationException() // 叶子节点
};
// remove
public void remove(Component child) {};
}
public class client () {
public static void main (String args[]) {
Component root = new Composite();
Component c1 = new Composite();
Component c2 = new Composite();
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
root.add(c1);
root.add(c2);
c1.add(leaf1);
c2.add(leaf2);
root.remove();
}
}
但是JavaScript和Java不一样,编辑器不会去检查类型,所以我们不会弄那么个抽象类,JavaScript中实现组合模式的难点在于保证组合对象和普通对象有相同的方法,这通常需要鸭子类型的思想对其进行接口的检查。
组合模式的例子-扫描文件夹
有没有觉得文件夹和文件之间的关系很方法使用组合模式来描述,看看代码:
// Folder
var Folder = function(name) {
this.name = name;
this.files = [];
}
Folder.prototype.add = function(file) {
this.files.push(file);
}
Folder.prototype.scan = function() {
for (var i = 0, file, files = this.files; file = files[i++]) {
file.scan();
}
}
// File
var File = function(name){
this.name = name;
}
File.prototype.add = function(file) {
throw new Error('File can not add');
}
File.prototype.scan = function() {
console.log('scan ' + this.name);
}
例子:
var folder = new Folder('fe');
var folder1 = new Folder('html');
var folder2 = new Folder('css');
var folder3 = new Folder('js');
var file1 = new File('html权威指南');
var file2 = new File('css权威指南');
var file3 = new File('JavaScript权威指南');
folder1.add(file1);
folder2.add(file2);
folder3.add(file3);
folder.add(folder1);
folder.add(folder2);
folder.add(folder3);
最后如果需要变量,直接调用根节点的folder.scan()就可以啦,哈哈哈
需要注意的地方
组合模式不是父子关系
组合模式是一种HAS-A的关系,不是IS-A
对叶对象操作的一致性
组合模式除了要求组合对象和叶对象拥有相同接口之外,还要一个必要条件,就是对一组叶对象的操作必须举要一致性,例如要给员工发过节费,这种可以用组合模式,如果给今天过生日的同事发蛋糕,组合模式就没办法弄了
双向映射关系
对象直接如果不是严格意义上的层次结构,也是不适合组合模式的,例如发过节费,有些人存在于两个组,那不是可能受到两份么?
用职责链模式提高组合模式性能
在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许 表现得不够理想。有时候我们确实可以借助一些技巧,在实际操作中避免遍历整棵树,有一种现 成的方案是借助职责链模式