js常用设计模式6-组合模式 1,命令模式和组合模式的联合应用 2,分析一下组合模式 3,更强大的宏命令 4,组合模式的实例-扫描文件夹 5,一些需要注意的地方 6,叶对象引用父对象 7,小结

组合模式和命令模式有点像,命令模式是一个个小的指令,而组合模式是一些小指令组合成的大指令

1,命令模式和组合模式的联合应用

试想这么一个场景:我们回家之后先关门,然后开电脑,最后打开QQ
关门,开电脑,开QQ是三个命令,现在我们用MacroCommand函数把他们组合起来,得到一个对象macroCommand ,通过macroCommand 来操作所有的命令。

  • macroCommand 被称作组合对象,它实际上是真正的命令数组commandList的“代理”。当然macroCommand不是代理,它只负责传递请求给真正的命令函数。
  • 关门,开电脑,开QQ都是叶对象。
var closeDoorCommand = {
  execute: function () {
    console.log('关门')
  }
}

var openPCCommand = {
  execute: function () {
    console.log('开电脑')
  }
}

var openQQCommand = {
  execute: function () {
    console.log('开QQ')
  }
}

var MacroCommand = function () {
  return {
    commandList: [],
    add: function (command) {
      this.commandList.push(command)
    },
    execute: function () {
      for (var i = 0, command; command = this.commandList[i++];) {
        command.execute()
      }
    }
  }
}

var macroCommand = MacroCommand()

//宏命令包含了一组子命令,形成了树形结构
macroCommand.add(closeDoorCommand)
macroCommand.add(openPCCommand)
macroCommand.add(openQQCommand)

//macroCommand:它是一个组合对象,表现为命令,但实际上只是一组真正命令的代理
macroCommand.execute()

2,分析一下组合模式

在上面的例子中macroCommand是 closeDoorCommand、openPCCommand、openQQCommand这三个命令的组合对象,它们有一个共同点:都有execute函数。这个函数代表了组合和单个命令的一致性。
组合模式将对象组合成树形结构,以表示“部分-整体”的结构层次,加上execute实现的一致性,使得用户在使用的时候,可以忽略组合和单个命令的不同,直接调用就完事了。
对于一个遥控器而言,当我们按下一个键时,只关注它带来的结果,而不需要在意这个操作调用了多少命令,只要它有execute,那么它就是好命令。

3,更强大的宏命令

现在我们的遥控器,包含了关门、开电脑、开QQ这三个功能。现在我们需要一个超级遥控器,能控制家里所有的电器,包括:

  • 打开空调
  • 打开电视和音响
  • 关门、开电脑、开QQ
    这时候我们会发现,之前的macroCommand现在变成了一个组合对象的一部分。
/**
 * 正题
 * 组合模式就是组合了一堆命令,可以统一调用,而忽略单个命令
 */

//更强大的宏命令--只要有execute,你就是他的一员,进行深度遍历

var MacroCommand = function () {
  return {
    commandList: [],
    add: function (command) {
      this.commandList.push(command)
    },
    execute: function () {
      for (var i = 0, command; command = this.commandList[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)

macroCommand.execute()


错误处理:
//缺点:叶节点可能会使用add方法,需要错误处理
var openAcCommand = {
  execute: function () {
    console.log('开空调')
  },
  add: function () {
    throw new Error('叶节点不能添加子对象')
  }
}
openAcCommand.add()

4,组合模式的实例-扫描文件夹

文件夹和文件之间的联系,非常适合用组合模式来描述(个人觉得dom节点的关系也很适合)。文件夹里既可以包含文件,又可以包含其他文件夹,最终形成了一棵树。组合模式对于文件夹应用有以下两个好处:

  • 复制文件夹所有内容的时候,只需要复制最外层的文件夹就行了
  • 用杀毒软件扫描文件夹的时候,不需要关心文件夹里面有多少文件夹或者文件,直接扫描最上层文件夹就可以了。
    现在,我们先定义文件夹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; file = this.files[i++];) {
    file.scan()
  }
}

/*********** Folder   ***********/
var File = function (name) {
  this.name = name
}
File.prototype.add = function () {
  throw new Error('文件下面不能添加文件')
}
File.prototype.scan = function () {
  console.log('开始扫描文件:' + this.name)
}

然后,创建文件夹和文件,将其组合成一棵树,这个结构就是我们硬盘里的文件目录结构:

var mainFolder = new Folder('学习资料')
var folder1 = new Folder('vue资料')
var folder2 = new Folder('react资料')

var file1 = new File('vue api')
var file2 = new File('vue 生命周期')
var file3 = new File('react-router')
var file4 = new File('设计模式')


folder1.add(file1)
folder1.add(file2)
folder2.add(file3)

mainFolder.add(folder1)
mainFolder.add(folder2)
mainFolder.add(file4)

mainFolder.scan()

通过这个例子我们可以看到,当我们需要遍历整个文件夹时,只需要调用最上层文件夹的scen方法:mainFolder.scan()。在新增文件时,用户也不需要关心它们具体是文件夹还是文件,直接添加进去就完事了。

5,一些需要注意的地方

  • 组合模式是聚合关系,而不是父子关系,因为叶对象(最开始的单个命令)不是组合对象的子类。组合对象可以把请求委托给它的所有叶对象,因为它们有相同的接口(和dom树很像)。
  • 组合模式的应用场景:必须要每个节点都有相同的接口,以及操作一致性。比如之前的例子,现在每个文件节点都有scan方法,但是如果有的文件有删除方法,有的没有,那么组合模式就不适用,要么都有,那么都没有。
  • 双向映射关系:一个节点只能属于一个组合对象,不能同时属于两个,比如一个文件,只能有一个直接的文件夹来包含,不可能同时有两个。如果一个人属于开发组,同时又属于测试组,这种交叉情况就不适用于组合模式。
  • 组合模式的对象关系和职责链模式很像。

6,叶对象引用父对象

之前的例子中,只能从父对象到叶对象,反过来是不行的。但是,当我们要删除某个文件的时候,我们需要知道它具体属于哪个文件夹,实际是从上层文件夹中删除文件的。
首先改写Folder类和File类,增加parent属性,在add函数中设置parent:

var Folder = function (name) {
  this.name = name
  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; file = this.files[i++];) {
    file.scan()
  }
}

给文件夹添加删除功能。
如果this.parent === null,要么它就是根节点,要么是还没有添加到树中,这种情况先暂时return,不作处理。
否则的话,该文件夹有父节点,那么就遍历父节点的所有子节点,找个需要删除的子节点,直接删除。

//添加删除功能。
Folder.prototype.delete = function () {
  if (this.parent === null) {
    return
  }
  for (var i = 0, files = this.parent.files; file = files[i]; i++) {
    if (file === this) {
      files.splice(i, 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.delete = function () {
  if (this.parent === null) {
    return
  }
  for (var i = 0, files = this.parent.files; file = files[i]; i++) {
    if (file === this) {
      files.splice(i, 1)
    }
  }
}

最后,我们来try一try:

var folder = new Folder('学习资料')
var folder1 = new Folder('vue')
var folder2 = new Folder('react')

var file1 = new File('vue api')
var file2 = new File('react api')
var file3 = new File('设计模式')

folder1.add(file1)
folder2.add(file2)
folder.add(folder1)
folder.add(folder2)
folder.add(file3)


folder1.delete()
folder.scan()

7,小结

何时使用组合模式:

  1. 表示对象的“部分-整体”结构层次。组合模式构造了一棵树,来表示对象的“部分-整体”结构,用户不需要知道树到底有多少层,只需要请求最顶层的节点,就可以对整棵树做统一的操作。
  2. 树中的所有对象都一致。客户不需要关注一个节点到底是组合对象还是叶对象,因为它们有相同的方法,能用就完事了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章