组合模式是一种结构型设计模式,可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用组合在一起的各个对象,且使用组合体和使用各对象是一样的效果;
模式特点:
- 树叶对象对外接口保存一致(操作与数据结构一致)
- 调用顶层对象,会自行遍历其下的叶对象执行
使用案例:
下面我们用组合模式来实现一个常见的例子,文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树。
// 树对象 - 文件夹
class CFolder {
constructor(name) {
this.name = name;
this.files = [];
}
add(file) {
this.files.push(file);
}
scan() {
for (let file of this.files) {
file.scan();
}
}
}
// 叶对象 - 文件
class CFile {
constructor(name) {
this.name = name;
}
add(file) {
throw new Error('文件下面不能再添加文件');
}
scan() {
console.log(`开始扫描文件:${this.name}`);
}
}
let mediaFolder = new CFolder('娱乐');
let movieFolder = new CFolder('电影');
let musicFolder = new CFolder('音乐');
let file1 = new CFile('钢铁侠.mp4');
let file2 = new CFile('再谈记忆.mp3');
movieFolder.add(file1);
musicFolder.add(file2);
mediaFolder.add(movieFolder);
mediaFolder.add(musicFolder);
mediaFolder.scan();
优缺点:
优点:
-
可以利用多态和递归机制更方便的使用复杂树结构
-
解耦调用者与复杂元素之间的联系,处理方式变得简单
-
满足开闭原则:
缺点:包裹对象创建太多,额外增加内存负担
总结:只要是树形结构或者只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,便可考虑使用组合模式。
一些需要注意的地方
- 组合模式是聚合关系,而不是父子关系,因为叶对象(最开始的单个命令)不是组合对象的子类。组合对象可以把请求委托给它的所有叶对象,因为它们有相同的接口(和dom树很像)。
- 组合模式的应用场景:必须要每个节点都有相同的接口,以及操作一致性。比如之前的例子,现在每个文件节点都有scan方法,但是如果有的文件有删除方法,有的没有,那么组合模式就不适用,要么都有,那么都没有。
- 双向映射关系:一个节点只能属于一个组合对象,不能同时属于两个,比如一个文件,只能有一个直接的文件夹来包含,不可能同时有两个。如果一个人属于开发组,同时又属于测试组,这种交叉情况就不适用于组合模式。
- 组合模式的对象关系和职责链模式很像。