7_组合模式

1 简介

  • 用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的

回顾宏命令

  • marcoCommand 被称为组合对象
    • closeDoorCommandopenPcCommandopenQQCommand 都是叶对象
  • marcoCommandexecute 并不执行真正的操作
    • 而是将真正的 execute 请求委托给叶对象
  • marcoCommand 作为代理,只负责传递请求

宏命令中包含了一组子命令,它们组成了一个树形结构,这里是一棵结构非常简单的树

2 组合模式的用途

1. 表示树形结构
  • 通过调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法

方便地描述对象 部分-整体 层次结构

2. 利用对象多态性,统一对待组合对象和单个对象

3 更强大的宏命令

实现超级万能遥控,能控制家里所有电器,有如下功能

  • 打开空调
  • 打开电视和音箱
  • 关门、开电脑、登录QQ

3.1 定义宏命令

class MacroComamnd {
  constructor() {
    this.commandsList = []
  }
  add(command) {
    this.commandsList.push(command)
  }
  execute() {
    this.commandsList.map(command => command.execute())
  }
}

3.2 命令A

let openAcCommand = {
  execute() {
    console.log('打开空调');
  }
}

3.3 命令B

let openTVCommand = {
  execute() {
    console.log('打开电视');
  }
}
let openSoundCommand = {
  execute() {
    console.log('打开音响');
  }
}
let macroCommand1 = new MacroComamnd()
macroCommand1.add(openTVCommand)
macroCommand1.add(openSoundCommand)

3.4 命令C

let closeDoorCommand = {
  execute() {
    console.log('关门');
  }
}
let openPcCommand = {
  execute() {
    console.log('开电脑');
  }
}
let openQQCommand = {
  execute() {
    console.log('登录QQ');
  }
}

let macroCommand2 = new MacroComamnd()
macroCommand2.add(closeDoorCommand)
macroCommand2.add(openPcCommand)
macroCommand2.add(openQQCommand)

3.5 超级命令

let macroCommand = new MacroComamnd()
macroCommand.add(openAcCommand)
macroCommand.add(macroCommand1)
macroCommand.add(macroCommand2)

3.6 事件绑定

let setCommand = (command => {
  document.getElementById('btn').onclick = () => {
    command.execute()
  }
})(macroCommand)
  • 基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合 --> 形成树结构
  • 调用最上层对象的 execute() 就能让整棵树运转起来

每当对最上层对象进行一次请求,实际上是对整棵树进行深度优先的搜索

4 抽象类在组合模式中的作用

  • 可以一致地对待组合对象和基本对象

5 透明性带来的安全问题

  • 误操作时需要抛出异常
let openTvCommand = {
  execute() {
    console.log('打开电视');
  },
  add() {
    throw new Error('叶对象不能添加子节点')
  }
}
let macroCommand = new MacroCommand()
macroCommand.add(openTvCommand)
openTvCommand.add(macroCommand)

6 组合模式的例子 -- 扫描文件夹

文件夹里既可以包含文件,又可以包含其它文件夹,最终组成一棵树

6.1 定义Folder类

class Folder {
  constructor(name) {
    this.name = name
    this.files = []
  }
  add(file) {
    this.files.push(file)
  }
  scan() {
    console.log('开始扫描文件夹' + this.name)
    this.files.map(file => file.scan())
  }
}

6.2 定义File类

class File {
  constructor(name) {
    this.name = name
  }
  add() {
    throw new Error('文件下面不能再添加文件')
  }
  scan() {
    console.log('开始扫描文件' + this.name)
  }
}

6.3 应用

let folder = new Folder('学习资料')
let folder1 = new Folder('js')
let folder2 = new Folder('vue')

let file1 = new File('学习资料文件')
let file2 = new File('js文件')
let file3 = new File('vue文件')

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

7 一些值得注意的地方

1. 组合模型不是父子关系

组合模型是一种聚合关系

  • 组合对象把请求委托给它所包含的所有叶对象
  • 组合对象与叶对象能够合作的关键是拥有相同的接口
2. 对叶对象操作的一致性
3. 双向映射关系
  • 某位架构师既隶属于开发组,又隶属于架构组,对象之间的关系并不是严格意义上的层次结构
  • 此时必须给父节点和子节点建立双向映射关系,一个简单的方法是给小组和员工对象都增加集合来保存对方的引用
  • 但是这种相互间的引用相当复杂,而且对象之间产生了过多的耦合性,修改或者删除一个对象都变得困难,此时我们可以引入中介者模式来管理这些对象
4. 用职责链模式提高组合模式性能
  • 借用职责链模式 避免遍历整棵树

8 引用父对象

  • this.parent

有时候我们需要在子节点上保持对父节点的引用,比如在组合模式中使用职责链时,有可能需要让请求从子节点往父节点上冒泡传递。还有当我们删除某个文件的时候,实际上是从这个文件所在的上层文件夹中删除该文件的

8.1 改写Folder类

class Folder {
  constructor(name) {
    this.name = name
    this.parent = null
    this.files = []
  }
  add(file) {
    this.parent = this
    this.files.push(file)
  }
  scan() {
    console.log('开始扫描文件夹' + this.name)
    this.files.map(file => file.scan())
  }
  remove() {
    // 根节点或者树外的游离节点
    if(!this.parent) return
    let files = this.parent.files;
    for(let i = files.length-1; i >= 0; i--) {
      if(files[i] = this) {
        files.splice(i, 1)
      }
    }
  }
}

8.2 改写File类

class File {
  constructor(name) {
    this.name = name
    this.parent = null
  }
  add() {
    throw new Error('文件下面不能再添加文件')
  }
  scan() {
    console.log('开始扫描文件' + this.name)
  }
  remove() {
    // 根节点或者树外的游离节点
    if(!this.parent) return
    let files = this.parent.files;
    for(let i = files.length-1; i >= 0; i--) {
      if(files[i] = this) {
        files.splice(i, 1)
      }
    }
  }
}

8.3 应用

let folder = new Folder('学习资料')
let folder1 = new Folder('js')
let file1 = new File('vue文件')

folder1.add(new File('js设计模式'))
folder.add(file1)
folder1.remove()
folder.scan()

9 何时使用组合模式

  1. 表示对象的部分-整体层次结构
  2. 客户希望统一对待树中的所有对象
posted on 2023-05-09 10:50  pleaseAnswer  阅读(106)  评论(0编辑  收藏  举报