组合模式
首发于:2022-03-29
基本概念
组合模式 (Composite Pattern)又叫整体-部分模式,它允许你将对象组合成树形结构来表现整体-部分层次结构,让使用者可以以一致的方式处理组合对象以及部分对象。
现实生活中的例子
组合模式最典型的例子就是文件夹了,文件夹下可以有子文件夹,也可以有文件,这是一种树形结构,层级结构分明。一般来说,文件夹是一个组合对象对应整体,文件是一个叶子对象对应部分,搜索文件的时候,通常我们希望把文件夹下所有的子孙对象都列出来。
公司的组织架构也是类似这种模式,公司下面可以有部门、子公司,再往下可以有二级部门、三级部门、四级部门甚至五级部门,每个部门里面又有各种团队、小组等。当领导希望传达什么会议精神啊或者企业价值观的时候,每个部门每个团队每个小组都会一级一级把这些东西传递下来,执行下来。
在类似的场景中,有以下特点:
- 结构呈整体-部分的树形关系,整体部分一般称为组合对象,组合对象下还可以有组合对象和叶对象;
- 组合对象和叶对象有一致的接口和数据结构,以保证操作一致;
- 请求从树的最顶端往下传递,如果当前处理请求的对象是叶对象,叶对象自身会对请求作出相应的处理;如果当前处理的是组合对象,则遍历其下的子节点(叶对象),将请求继续传递给这些子节点;
应用场景
组合模式在前端也是广泛应用,从 <html>
根节点,到 <head>
、<body>
、<div>
、<p>
等等都是类似的结构,有着类似的操作方式。
React
源码中采用的 Fiber
架构,其实也用到了组合模式,当然 vue
中的 VNode
也是。
组合模式应用场景的一般具有以下特点:
- 如果对象组织呈树形结构就可以考虑使用组合模式,特别是如果操作树中对象的方法比较类似时;
- 使用者希望统一对待树形结构中的对象,比如用户不想写一堆
if-else
来处理树中的节点时,可以使用组合模式;
优缺点
优点:
- 由于组合对象和叶对象具有同样的接口,因此调用的是组合对象还是叶对象对使用者来说没有区别,使得使用者面向接口编程;
- 如果想在组合模式的树中增加一个节点比较容易,在目标组合对象中添加即可,不会影响到其他对象,对扩展友好,符合开闭原则,利于维护;
缺点:
- 增加了系统复杂度,如果树中对象不多,则不一定需要使用;
- 如果通过组合模式创建了太多的对象,那么这些对象可能会让系统负担不起;
实现
js
/* 文件夹类 */
class Folder {
constructor(name, children) {
this.name = name
this.children = children
}
/* 在文件夹下增加文件或文件夹 */
add(...fileOrFolder) {
this.children.push(...fileOrFolder)
return this
}
/* 扫描方法 */
scan(cb) {
this.children.forEach(child => child.scan(cb))
}
}
/* 文件类 */
class File {
constructor(name, size) {
this.name = name
this.size = size
}
/* 在文件下增加文件,应报错 */
add(...fileOrFolder) {
throw new Error('文件下面不能再添加文件')
}
/* 执行扫描方法 */
scan(cb) {
cb(this)
}
}
const foldMovies = new Folder('电影', [
new Folder('漫威英雄电影', [
new File('钢铁侠.mp4', 1.9),
new File('蜘蛛侠.mp4', 2.1),
new File('金刚狼.mp4', 2.3),
new File('黑寡妇.mp4', 1.9),
new File('美国队长.mp4', 1.4)]),
new Folder('DC英雄电影', [
new File('蝙蝠侠.mp4', 2.4),
new File('超人.mp4', 1.6)])
])
console.log('size 大于2G的文件有:')
foldMovies.scan(item => {
if (item.size > 2) {
console.log(`name:${ item.name } size:${ item.size }GB`)
}
})
结果
size 大于2G的文件有:
name:蜘蛛侠.mp4 size:2.1GB
name:金刚狼.mp4 size:2.3GB
name:蝙蝠侠.mp4 size:2.4GB