《js 设计模式与开发实践》读书笔记 10

组合模式,我们在第 9 章命令模式中将结果宏命令的结构和作用。宏命令对象包含了一组子命令对象,不管是宏命令对象,还是子命令对象,都有一个 execute 方法负责执行命令。
var closeDoorCommand = {
execute: function () {
console.log('关门')
}
}
var openPcCommand = {
execute: function () {
console.log('开电脑')
}
}
var openQQCommand = {
execute: function () {
console.log('登录qq')
}
}
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command)
},
execute: function () {
for (let i = 0; i < this.commandsList.length; i++) {
const command = this.commandsList[i]
command.execute()
}
}
}
}
var marcroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()
通过观察这段代码,我们很容易发现,宏命令包含了一组子命令,它们组成了一个树形结构。其中 marcoCommand 被称为组合对象,closeDoorCommand,opoenPcCommand,openQQCommand 都是叶对象。在 macroCommand 的 execute 方法里,并不执行真正的操作,而是遍历它所包含的叶对象,把真正的 execute 请求委托给这些叶对象。macroCommand 表现得像一个命令,但它实际上只是一组真正命令的代理。并非真正的代理,虽然结构上相似,但 macroCommand 只负责传递请求给叶对象,它的目的不在于控制对叶对象的访问。
组合模式将对象组合成树形结构,以表示部分-整体的层次结构。除了用来表示属性结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象具有一致性:我们很容易找到组合模式的一个有点:提供了一种遍历树形结构的方案,通过调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法,所以我们遥控器一次操作,便会依次完成多件事情。组合模式可以非常方便地描述对象部分-整体层次结构。利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。当宏命令和普通子命令接收到执行 execute 方法的请求时,宏命令和普通子命令都会做它们各自认为正确的事情。这些差异时隐藏在客户背后的,在客户看来,这种透明性可以让我们非常自由地扩展这个万能控制器。
在组合模式中,请求在树中传递的过程总是遵循一种逻辑。请求从上到下沿着树进行传递,直到树的尽头。作为客户,只需要关心树最顶层的组合对象,客户只需要请求这个组合对象,请求便会沿着树往下传递,依次到达所有的叶对象。在刚才的例子中,由于宏命令和子命令组成的树太过简单,我们还不清楚地看到组合模式带来的好处。
目前的遥控器,包含了关门,开电脑,登录 qq 这三个命令。现在我们需要一个更强大的遥控器,可以控制家里所有的电器。这个遥控器有以下功能:打开空调,打开电视和音响。关门,开电脑,登录 qq。
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command)
},
execute: function () {
for (var i = 0; i < this.commandsList.length; i++) {
command = this.commandsList[i]
command.execute()
}
}
}
}
var openAcCommand = {
execute: function () {
console.log('打开空调')
}
}
var openTvCommand = {
execute: function () {
console.log('打开电视')
}
}
var openSoundCommand = {
execute: function () {
console.log('打开音响')
}
}
var macroCommand1 = MacroCommand()
macroCommand1.add(openTvCommand)
macroCommand1.add(openSoundCommand)
var closeDoorCommand = {
execute: function () {
console.log('关门')
}
}
var openPcCommand = {
execute: function () {
console.log('开电脑')
}
}
var openQQCommand = {
execute: function () {
console.log('登录qq')
}
}
var macroCommand2 = MacroCommand()
macroCommand2.add(closeDoorCommand)
macroCommand2.add(openPcCommand)
macroCommand2.add(openQQCommand)
var macroCommand = MacroCommand()
macroCommand.add(openAcCommand)
macroCommand.add(macroCommand1)
macroCommand.add(macroCommand2)
document.getElementById('button1').onclick = function () {
macroCommand.execute()
}
从这个例子中可以看到,基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合,这样不断递归下去,这棵树的结构可以支持任意多的复杂度。在树最中被构造完成之后,让整棵树最终运转起来的步骤非常简单,只需要调用最上层对象的 execute 方法。每当对最上层的对象进行一次请求时,实际上是在对整个树进行深度优先的搜索,而创建组合对象的程序员并不关心这些内在的细节,往这棵树里面添加一些新的节点对象是非常容易的事情。
组合模式的例子。文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组成一棵树,组合模式在文件夹的应用有两层好处。比如我们像复制文件夹,组合模式让 ctrl+c +v 称为一个统一的操作。当你用杀毒软件扫描该文件夹时,往往不会关心里面有多少文件和子文件夹,组合模式让我们只需要操作最外层的文件夹进行扫描。
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 (let i = 0; i < this.files.length; i++) {
const file = this.files[i]
file.scan()
}
}
var File = function (name) {
this.name = name
}
File.prototype.add = function () {
throw new Error('文件下面不能再添加文件')
}
File.prototype.scan = function () {
console.log('开始扫描文件:' + this.name)
}
var folder = new Folder('学习资料')
var folder1 = new Folder('Js')
var folder2 = new Folder('jq')
var file1 = new File('js设计模式')
var file2 = new File('精通jq')
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()
在使用组合模式的时候,还有几个值得我们注意的地方。1,组合模式不是父子关系。组合模式是一种has-a的关系,不是is-a的关系。2,对叶对象操作的一致性。组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性,3,双向映射关系。一个员工属于多个架构的话,可能会执行多次。这种复合情况下我们必须给父节点和子节点建立双向映射关系,一个简单的方法是给小组和员工对象都增加集合来保存对方的引用。4.在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许表现的不够理想。有时候我们确实可以借助一些技巧,在世纪操作中避免遍历整棵树,有一种现成的方案是借助职责链模式。
组合模式如果运用得当,可以大大简化代码。一般适用这两种情况。表示对象的部分-整体层次结构。客户希望统一对待书中的所有对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~