# 组合模式
组合模式是又称整体-部分模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。
组合模式将对象组合成树形结构中,以表示“整体-部分”的层次结构,每当对最上层的对象进行一次请求时,实际上是在对整个树形结构进行深度优先遍历。利用对象的多态性表现,组合模式可以使客户端忽略组合对象和单个对象的不同,对它们进行统一处理。其次,往树形结构里添加新的节点也很容易,客户端不会因为加入了新的对象而更改源代码,满足开放-封闭原则。
# JavaScript 中的组合模式
静态类型语言,如 Java 中实现组合模式的关键是 Composite
类和 Leaf
类都必须继承自一个 Component
抽象类。这个 Component
类既代表组合对象,又代表叶对象,它能够保证组合对象和叶对象拥有同样名字的方法,从而可以对同一消息都做出反馈。组合对象和叶对象的具体类型被隐藏在 Component
抽象类身后。
在 JavaScript 中,对象的多态性是与生俱来的,因此在 JavaScript 中实现组合模式不需要抽象类,只要保证组合对象和叶对象拥有同样的方法。
# 实现组合模式
文件夹和文件之间的关系非常适合使用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树。
这里使用组合模式模拟杀毒软件的一次扫描杀毒操作,代码如下:
// 文件夹构造函数
const Folder = function (name) {
this.name = name
this.parent = null
this.files = []
}
// 添加文件或文件夹
Folder.prototype.add = function (file) {
this.files.push(file)
file.parent = this
return this
}
// 扫描文件夹
Folder.prototype.scan = function (callback) {
this.files.forEach((file) => file.scan(callback))
}
// 删除文件夹
Folder.prototype.remove = function () {
if (!this.parent) return
const files = this.parent.files
for (let i = files.length; i >= 0; i--) {
if (files[i] === this) {
files.splice(i, 1)
}
}
}
// 文件类
const File = function (name) {
this.name = name
this.parent = null
}
File.prototype.add = function () {
throw new Error('不能往文件里添加文件')
}
// 扫描文件
File.prototype.scan = function (callback) {
if (callback) {
callback(this)
} else {
console.log(this.name)
}
}
// 删除文件
File.prototype.remove = function () {
if (!this.parent) return
const files = this.parent.files
for (let i = files.length; i >= 0; i--) {
if (files[i] === this) {
files.splice(i, 1)
}
}
}
const books = new Folder('books')
const js = new Folder('js')
const css = new Folder('css')
books.add(js).add(css)
const virus = new File('virus')
books.add(virus) // 添加病毒文件
js.add(new File('学习 JavaScript 数据结构与算法(第 3 版)'))
css.add(new File('CSS 揭秘'))
books.scan((file) => {
if (file.name === virus.name) {
console.log(`发现病毒:${file.name}`)
file.remove()
console.log('已清除...')
} else {
console.log(file.name)
}
})
// 学习 JavaScript 数据结构与算法(第 3 版)
// CSS 揭秘
// 发现病毒:virus
// 已清除...
可以发现,当对 books
文件夹进行扫描时,只需要执行该对象的 scan()
方法,即可对其包含的所有文件夹及文件统一进行扫描操作。其次,增加和删除文件非常方便,不需要修改额外代码,符合开放-封闭原则。