[TOC]
# 組合模式
組合模式是將對(duì)象組合成樹形結(jié)構(gòu),以表示“部分-整體”的層次結(jié)構(gòu)。
在大多數(shù)情況下,我們都可以忽略掉組合對(duì)象和單個(gè)對(duì)象之間的差別,從而用一致的方式來處理它們。
- 表示樹形結(jié)構(gòu):提供了一種遍歷樹形結(jié)構(gòu)的方案,通過調(diào)用組合對(duì)象的execute方法,程序會(huì)遞歸調(diào)用組合對(duì)象下面的葉對(duì)象的execute方法。組合模式可以非常方便地描述對(duì)象部分-整體層次結(jié)構(gòu)。
- 利用對(duì)象多態(tài)性統(tǒng)一對(duì)待組合對(duì)象和單個(gè)對(duì)象
## 請(qǐng)求在樹中傳遞的過程
請(qǐng)求從樹最頂端的對(duì)象往下傳遞,當(dāng)前處理請(qǐng)求的對(duì)象是葉對(duì)象的話,葉對(duì)象自身會(huì)對(duì)請(qǐng)求做出相應(yīng)的處理;如果當(dāng)前請(qǐng)求的對(duì)象是組合對(duì)象,組合對(duì)象則會(huì)遍歷它屬下的子節(jié)點(diǎn),將請(qǐng)求繼續(xù)傳遞給這些子節(jié)點(diǎn)。
總而言之,子節(jié)點(diǎn)是葉對(duì)象,葉對(duì)象自身會(huì)處理這個(gè)請(qǐng)求,而如果子節(jié)點(diǎn)還是組合對(duì)象,請(qǐng)求會(huì)繼續(xù)往下傳遞。
## 更強(qiáng)大的宏命令
```javascript
var MacroCommand = function(){
return {
commandList: [],
add: function(command){
this.commandsList.push(command);
},
execute: function(){
for(var i=0, command; command = this.commandList[i++];){
command.execute();
}
}
}
};
var openAcCommand = {
execute: function(){
...
}
};
// Tv、Sound 是相連的
var openTvCommand = {
execute: function(){
...
}
};
var openSoundCommand = {
execute: function(){
...
}
};
var macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);
// closeDoor、openPC、QQ 是相連的
var closeDoorCommand = {
execute: function(){
...
}
};
var openPCCommand = {
execute: function(){
...
}
};
var openQQCommand = {
execute: function(){
...
}
};
var macroCommand2 = MacroCommand();
macroCommand2.add(closeDoorCommand);
macroCommand2.add(openPCCommand);
macroCommand1.add(openQQCommand);
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
macroCommand.execute();
```
基本對(duì)象可以被組合成更復(fù)雜的組合對(duì)象,組合對(duì)象又可以被組合,這樣不斷遞歸下去,這棵樹的結(jié)構(gòu)可以支持任意多的復(fù)雜度。實(shí)際上是對(duì)整個(gè)樹進(jìn)行深度優(yōu)先的搜索。
## 抽象類在組合模式中的作用
在`JavaScript`這種動(dòng)態(tài)語言中,對(duì)象的多態(tài)性是與生俱來的。`JavaScript`中實(shí)現(xiàn)組合模式的難點(diǎn)在于要保證組合對(duì)象和葉對(duì)象擁有同樣的方法,通常需要用鴨子類型的思想對(duì)它們進(jìn)行接口檢查。
## 透明性帶來的安全問題
組合對(duì)象可以擁有子節(jié)點(diǎn),葉對(duì)象下面就沒有子節(jié)點(diǎn),所以會(huì)發(fā)生一些誤操作。解決方案通常從是也給葉對(duì)象增加對(duì)應(yīng)的方法,并且在調(diào)用的時(shí)候拋出異常。
```javascript
var MacroCommand = function(){
return {
commandList: [],
add: function(command){
this.commandsList.push(command);
},
execute: function(){
for(var i=0, command; command = this.commandList[i++];){
command.execute();
}
}
}
};
var openAcCommand = {
execute: function(){
...
},
add: function(){ // 增加相同的方法并拋出異常
throw new Error('葉對(duì)象不能添加子節(jié)點(diǎn)');
}
};
```
## 一些值得注意的地方
- 組合模式不是父子關(guān)系:組合模式是一種`HAS-A 聚合`的關(guān)系,不是`IS-A`。
- 對(duì)葉對(duì)象操作的一致性:對(duì)一組葉對(duì)象的操作必須具有一致性,只有用一致的方法對(duì)待列表中的每個(gè)葉對(duì)象的時(shí)候,才適合使用組合模式。
- 雙向映射關(guān)系:引入中介者模式來管理。
- 用職責(zé)鏈模式提高組合模式性能:組合模式中,父對(duì)象和子對(duì)象之間實(shí)際上形成了天然的職責(zé)鏈。讓請(qǐng)求順著鏈條從父對(duì)象往子對(duì)象傳遞,或者是反過來從子對(duì)象往父對(duì)象傳遞,直到遇到可以處理該請(qǐng)求的對(duì)象為止。
## 引用父對(duì)象
有時(shí)需要在子節(jié)點(diǎn)上保持對(duì)父節(jié)點(diǎn)的引用,實(shí)際上是從子節(jié)點(diǎn)的父節(jié)點(diǎn)中查找該節(jié)點(diǎn)。
```javascript
var Folder = function(name){
this.name = name;
this.parent = null;
this.files = [];
};
Folder.prototype.add = function(file){
file.parent = this; // 設(shè)置父對(duì)象
this.files.push(file);
};
Folder.prototype.remove = function(){
if(!this.parent){ // 根節(jié)點(diǎn)或者樹外的游離節(jié)點(diǎn)
return;
}
for(var files = this.parent.files, l=files.length - 1; l >=0; l--){
var file = files[l];
if(file === this){
files.splice(l,1);
}
}
};
```
首先會(huì)判斷`this.parent`,其次再遍歷父節(jié)點(diǎn)中保存的子節(jié)點(diǎn)列表,找到先要?jiǎng)h除的子節(jié)點(diǎn)。
## 何時(shí)使用組合模式
- 表示對(duì)象的部分-整體層次結(jié)構(gòu):在開發(fā)期間不確定這棵樹到底存在多說層次的時(shí)候,在樹的構(gòu)造最終完成之后,只需要通過請(qǐng)求樹的最頂層對(duì)象,便能對(duì)整棵樹做統(tǒng)一的操作。
- 客戶希望統(tǒng)一對(duì)待樹中的所有對(duì)象。組合模式使客戶可以忽略組合對(duì)象和葉對(duì)象的區(qū)別,不用關(guān)心當(dāng)前正在處理的對(duì)象是組合對(duì)象或者葉對(duì)象,不需要寫一堆`if`、`else`語句來分別處理它們。
在組合模式中可能會(huì)產(chǎn)生這樣一個(gè)系統(tǒng):系統(tǒng)中的每個(gè)對(duì)象看起來都與其它對(duì)象差不多。它們的區(qū)別只有在運(yùn)行的時(shí)候才會(huì)顯現(xiàn)出來,這會(huì)使代碼難以理解。此外,如果通過組合模式創(chuàng)建了太多的對(duì)象,那么這些對(duì)象可能會(huì)讓系統(tǒng)負(fù)擔(dān)不起。
