组合模式

2016-10-12 曹强 更多博文 » 博客 » GitHub »

javascript

原文链接 https://ronghuaxueleng.github.io/2016/10/12/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5-12%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


组合模式


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

请求在树中传递的过程

在组合模式中,请求在树中传递的过程总是遵循一种逻辑。
请求从树的最顶端的对象往下传递,如果当前处理请求的对象是叶对象,叶对象自身会对请求做相互相应的处理;如果当前处理请求对象是组合对象,组合对象则会遍历它下面的节点,将请求继续传递。
总之,组合对象的请求是从上到下沿着树传递,直到树的尽头。作为客户,只要关心树最顶层的组合对象,客户只需要请求这个组合对象,请求便会向下延续。

现在,假设一个需求,我们需要一个超级遥控器可以控制家里所有的电器,且拥有以下功能

  • 打开空调

  • 打开电视和音响

  • 关门,开电脑,登录QQ

依旧,废话不多说,show the code

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();
macroCommand1.add( closeDoorCommand );
macroCommand1.add( openPcCommand );
macroCommand1.add( openQQCommand );

/*现在把所有命令组合成一个超级命令*/
var macroCommand = MacroCommand();
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);

一些安全问题

组合模式的透明性使得发起请求的用户不用估计树中组合对象和叶对象的区别,但他们在本质上是有区别的。
组合对象可以拥有子节点,叶对象下面就没有子节点,所以我们也许会发生一些误操作,比如往叶对象中添加子节点。解决方法给叶对象也添加一个add操作,但是在调用时候会抛出一个异常来及时提醒客户

var openAcCommand = {
  execute: function() {
    console.log('打开空调');
  },
  add: function() {
    throw new Error('叶对象不能添加子节点');
  }
};

最后

一些值得注意的地方:

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

  2. 对叶对象操作的一致性

  3. 双向映射关系

  4. 用职责链模式提高组合模式性能

一般应用情况:

  • 表示对象的部分-整体层次结构。 组合模式可以方便地构造一棵树来表示对象的部分-整体结构。特别是我们在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合统一-开放原则。

  • 客户希望统一对待树中所有对象。组合模式使客户可以忽略组合对象和叶对象的区别,客户在面对这棵树的时候,不用担心正在处理的对象是组合对象还是叶对象,也就不用写一堆ifelse语句来处理他们。组合对象和叶对象会各自做自己正确的事情,这是组合模式最重要的能力。