组合模式
2022年9月29日大约 5 分钟
组合模式
组合模式的用途
组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,下面分别说明。
提供了一种遍历树形结构的方案,通过调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、打开电脑、登录 QQ 这几件事情。组合模式可以非常方便地描述对象部分整体层次结构。
利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。
组合模式的例子——扫描文件夹
文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以
包含其他文件夹,最终可能组合成一棵树,组合模式在文件夹的应用中有以下两层好处。
- 例如,我在同事的移动硬盘里找到了一些电子书,想把它们复制到 F 盘中的学习资料文件夹。在复制这些电子书的时候,我并不需要考虑这批文件的类型,不管它们是单独的电子书还是被放在了文件夹中。组合模式让 Ctrl+V、Ctrl+C 成为了一个统一的操作。
2 .当我用杀毒软件扫描该文件夹时,往往不会关心里面有多少文件和子文件夹,组合模式使得我们只需要操作最外层的文件夹进行扫描。
// 现在我们来编写代码,首先分别定义好文件夹 Folder 和文件 File 这两个类。见如下代码:
/******************************* Folder ******************************/
var Folder = function (name) {
this.name = name;
this.files = [];
};
Folder.prototype.add = function (file) {
this.files.push(file);
};
Folder.prototype.scan = function () {
console.log('开始扫描文件夹: ' + this.name);
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 () {
throw new Error('文件下面不能再添加文件');
};
File.prototype.scan = function () {
console.log('开始扫描文件: ' + this.name);
};
// 接下来创建一些文件夹和文件对象, 并且让它们组合成一棵树,这棵树就是我们 F 盘里的现有文件目录结构:
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var folder2 = new Folder('jQuery');
var file1 = new File('JavaScript 设计模式与开发实践');
var file2 = new File('精通 jQuery');
var file3 = new File('重构与模式')
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);
// 现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设我们已经得到了这些文件对象:
var folder3 = new Folder('Nodejs');
var file4 = new File('深入浅出 Node.js');
folder3.add(file4);
var file5 = new File('JavaScript 语言精髓与编程实践');
// 接下来就是把这些文件都添加到原有的树中:
folder.add(folder3);
folder.add(file5);
//扫描整个文件
folder.scan();
// 开始扫描文件夹: 学习资料
// 开始扫描文件夹: JavaScript
// 开始扫描文件: JavaScript 设计模式与开发实践
// 开始扫描文件夹: jQuery
// 开始扫描文件: 精通 jQuery
// 开始扫描文件: 重构与模式
// 开始扫描文件夹: Nodejs
// 开始扫描文件: 深入浅出 Node.js
// 开始扫描文件: JavaScript 语言精髓与编程实践
引用父对象(删除某个文件)
/******************************* Folder ******************************/
var Folder = function (name) {
this.name = name;
this.parent = null;
//增加 this.parent 属性
this.files = [];
};
Folder.prototype.add = function (file) {
file.parent = this;
//设置父对象
this.files.push(file);
};
Folder.prototype.scan = function () {
console.log('开始扫描文件夹: ' + this.name);
for (var i = 0, file, files = this.files; file = files[i++];) {
file.scan();
}
};
Folder.prototype.remove = function () {
if (!this.parent) { //根节点或者树外的游离节点
return;
}
for (var files = this.parent.files, l = files.length - 1; l >= 0; l--) {
var file = files[l];
if (file === this) {
files.splice(l, 1);
}
}
};
/******************************* File ******************************/
var File = function (name) {
this.name = name;
this.parent = null;
};
File.prototype.add = function () {
throw new Error('不能添加在文件下面');
};
File.prototype.scan = function () {
console.log('开始扫描文件: ' + this.name);
};
// 在 File.prototype.remove 方法里,首先会判断 this.parent,如果 this.parent 为 null,那么这个文件夹要么是树的根节点,要么是还没有添加到树的游离节点,这时候没有节点需要从树中移除,我们暂且让 remove 方法直接 return,表示不做任何操作
File.prototype.remove = function () {
if (!this.parent) { //根节点或者树外的游离节点
return;
}
for (var files = this.parent.files, l = files.length - 1; l >= 0; l--) {
var file = files[l];
if (file === this) {
files.splice(l, 1);
}
}
};
// 测试一下移除文件功能
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var file1 = new Folder('深入浅出 Node.js');
folder1.add(new File('JavaScript 设计模式与开发实践'));
folder.add(folder1);
folder.add(file1);
folder1.remove();
//移除文件夹
folder.scan();
// 开始扫描文件夹: 学习资料
// 开始扫描文件夹: 深入浅出 Node.js
小结
组合模式可以让我们使用树形方式创建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上。在大多数情况下,我们都可以忽略掉组合对象和单个对象之间的差别,从而用一致的方式来处理它们。
然而,组合模式并不是完美的,它可能会产生一个这样的系统:系统中的每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候会才会显现出来,这会使代码难以理解。此外,如果通过组合模式创建了太多的对象,那么这些对象可能会让系统负担不起。