一、是什么
组合模式,又叫 “部分整体” 模式,将对象组合成树形结构,以表示 “部分-整体” 的层次结构。通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性
如下面的代码:
- 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();
上述是命令模式的一个应用,macroCommand命令叫做组合对象,其包含了closeDoorCommand、openPcCommand、openQQCommand三个叶对象
macroCommand 的 execute 方法里,并不执行真正的操作,而是遍历它所包含的叶对象,把真正的 execute 请求委托给这些叶对象
二、应用场景
组合模式应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方:
- 「命令分发:」 只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放-封闭原则;
- 「统一处理:」 统一对待树中的所有对象,忽略组合对象和叶对象的区别
如将上述例子稍复杂,当我们点击按钮时,出发一系列操作(打开空调,打开电视,打开音响)其中打开电视和打开音响是一组组合对象,如下代码:
- <button id=button>按我</button>
- <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 openAcCommend = {
- 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(openAcCommend)
- macroCommand.add(macroCommand1)
- macroCommand.add(macroCommand2)
- // 给超级遥控器绑定命令
- var setCommand = (function (command) {
- document.getElementById('button').onclick = function () {
- command.execute()
- }
- })(macroCommand)
- </script>
组合模式的透明性使得发起请求的客户不用去顾忌树中组合对象和叶对象的区别,但它们在本质上是有区别的。
组合对象可以拥有叶子节点,叶对象下面就没有子节点,所以我们可能会有一些误操作,比如试图往叶对象中添加子节点
解决方案就是给叶对象也增加 add 方法,并且在调用这个方法时,抛出一个异常来及时提醒用户,如下:
- 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 openAcCommend = {
- execute: function () {
- console.log('打开空调');
- },
- add: function() {
- throw new Error('叶对象不能添加子节点')
- }
- }
三、总结
组合模式常使用树形方式创建对象,如下图:
特点如下:
- 表示 “部分-整体” 的层次结构,生成 "树叶型" 结构
- 一致操作性,树叶对象对外接口保存一致(操作与数据结构一致)
- 自上而下的的请求流向,从树对象传递给叶对象
- 调用顶层对象,会自行遍历其下的叶对象执行
参考文献
https://www.runoob.com/design-pattern/composite-pattern.html
https://segmentfault.com/a/1190000019773556
https://juejin.cn/post/6995851145490989070