组合模式

114 阅读3分钟

组合模式就是用小的叶对象来构建更大的对象,而这些小的叶对象本身也许是由更小的“孙对象”构成的。

1. 回顾宏命令

//叶对象
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 (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute()
      }
    },
  }
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()

2. 组合的用途

组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,下面分别说明

  • 表示树形结构。通过回顾上面的例子,我们很容易找到组合模式的一个优点:提供了一种遍历树形结构的方案,通过调用组合对象的 execute 方法,程序会递归调用组合对象下面的叶对象的 execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、打开电脑、登录 QQ 这几件事情。组合模式可以非常方便地描述对象部分-整体层次结构。
  • 利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。

3. 更加强大的宏命令

目前的万能遥控器,包含了关门、开电脑、登录 QQ 这 3 个命令。现在我们需要一个“超级万能遥控器”,可以控制家里所有的电器,这个遥控器拥有以下功能:

  • 打开空调

  • 打开电视和音响

  • 关门、开电脑、登录 QQ

首先在节点中放置一个按钮 button 来表示这个超级万能遥控器,超级万能遥控器上安装了一个宏命令,当执行这个宏命令时,会依次遍历执行它所包含的子命令,代码如下:

<html>
  <body>
    <button id="button">按我</button>
  </body>
  <script>
    var MacroCommand = function () {
      return {
        commandsList: [],
        add: function (command) {
          this.commandsList.push(command)
        },
        execute: function () {
          for (var i = 0, command; (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)
    /*********关门、打开电脑和打登录 QQ 的命令****************/
    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)
    /*********最后给遥控器绑定“超级命令”**********/
    var setCommand = (function (command) {
      document.getElementById('button').onclick = function () {
        command.execute()
      }
    })(macroCommand)
  </script>
</html>

4. 注意点

  • 组合模式不是父子关系。组合模式是一种 HAS-A(聚合)的关系,而不是 IS-A。组合对象包含一组叶对象,但 Leaf并不是 Composite 的子类。组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。
  • 对叶对象操作的一致性。组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。
  • 双向的映射关系
  • 用职责链模式提高组合模式性能

5. 何时使用组合模式

  • 表示对象的部分-整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放封闭原则。
  • 客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆 if、else 语句来分别处理它们。组合对象和叶对象会各自做自己正确的事情,这是组合模式最重要的能力。