1 組合模式的定義
組合模式:用小的子物件來構成更大的物件,而這些小的物件本身也許是由更小的孫物件
構成的。
我理解的例如:一個人,是由肌肉+肥肉+骨頭+器官構成。而肌肉,肥肉等又是又化學物質構成,化學物質裡面還分為很多細小的鈣鐵鋅硒等元素組成....。由小到大最終組成了一個人。
2 巨集命令例子
巨集命令是一組命令的集合,通過執行巨集命令的方式,可以一次執行一批命令。家裡有一個萬能遙控器,每天回家的時候,只要按一個特別的按鈕,它就會幫我們關上房間門,順便開啟電視,開始煮飯。
var closeDoorCommand = {
execute: function() {
console.log('close door');
}
};
var openTVCommand = {
execute: function() {
console.log('open TV');
};
};
var cookCommand = {
execute: function() {
console.log('cook');
}
};
var MacroCommand = function() {
var cache = [];
return {
add: function(command) {
cache.push(command);
},
execute: function() {
cache.map(function(command) {
command.execute()
});
}
};
}
var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openTVCommand );
macroCommand.add( cookCommand );
macroCommand.execute();
複製程式碼
通過觀察這段程式碼,我們很容易發現,巨集命令中包含了一組子命令。看起來像是在執行macroCommand的execute方法,表現的像一個命令,但是實際只是一組命令的‘代理’,並非正真的代理,雖然結構相似,但是macroCommand只負責傳遞請求給葉子物件,不低不在於控制對葉子物件的訪問。
3 組合模式傳遞
- 組合模式將物件組合成為
樹形結構
,以表示部分-整體
的層次結構。通過呼叫組合物件的execute方法,程式便會遞迴呼叫組合物件下面的葉物件的execute方法。 - 組合物件與葉物件有物件多型性,客戶端可以忽略組合物件與單個物件的不同。在組合模式中,客戶將統一使用組合物件結構中的所有物件,而不需要關係它究竟是組合物件還是單個物件。
實際開發中,我們往組合物件中新增一個命令時,並不會關心這個命令時巨集命令還是普通名,只要它是一個命令並擁有execute方法,那麼就能被新增到組合物件(例如上面的萬能遙控器)中。當巨集命令和子命令接收到執行請求時,它們就會做自己認為正確的事。這些差異隱藏在客戶背後。對於客戶來說,只看到了組合物件(萬能遙控器)
組合模式的樹形結構
,讓它的呼叫遵循一種自上而下的請求順序規則。例如上面的巨集命令物件呼叫後,就會將它下面的子命令一個一個呼叫。如果子命令中還有子命令,也會繼續往下傳遞,直到不再有其他子節點。
4 更強大的巨集命令
- 基本物件可以被組合成為複雜的組合物件,組合物件又可以被組合,這樣不斷地遞迴下去,這棵樹可以支援任意多的複雜度。而想讓這棵樹執行起來很簡單,只需要執行execute方法就可以了。
- 組合模式的透明性讓客戶不用顧忌樹中組合物件與葉子物件的區別,但是實際葉子物件不能再新增子節點。因此為了保證一致性,可以為葉子節點也新增add方法並丟擲不能新增的提示資訊。
上面的萬能遙控器只有一層子節點。現在我們來實現一個多層節點的遙控器。
// 定義一個組合命令的功能函式
var MachroCommand = function() {
var cache = [];
return {
add: function(command) {
cache.push(command);
},
execute: function() {
cache.map(function(command) {
command.execute()
});
}
};
};
var closeDoorCommand = {
execute: function() {
console.log('close door');
},
add: function() {
throw Error('can\'t add child node');
}
};
var openTVCommand = {
execute: function() {
console.log('open TV');
},
add: function() {
throw Error('can\'t add child node');
}
};
var openAirConditionCommand = {
execute: function() {
console.log('open 冰箱');
},
add: function() {
throw Error('can\'t add child node');
}
};
var openComputerCommand = {
execute: function() {
console.log('open computer');
},
add: function() {
throw Error('can\'t add child node');
}
};
// 組合命令(開啟家電)
var openApplianceCommand = MachroCommand();
openApplianceCommand.add(openTVCommand);
openApplianceCommand.add(openAirConditionCommand);
// 組合命令(萬能遙控器)
var machroCommand = MachroCommand();
machroCommand.add(openApplianceCommand);
machroCommand.add(openComputerCommand);
machroCommand.add(closeDoorCommand);
// 呼叫萬能遙控器
machroCommand.execute();
複製程式碼
5 組合模式掃描資料夾
當我們將資料夾從A處複製到B處, 只需要使用Ctrl + C
, Ctrl + v
命令就可以了。而這裡來的複製和貼上就是組合命令,檔案的層級關係是:
- 資料夾(組合物件)可包含資料夾(組合物件)和檔案(子節點)
- 檔案不能包含檔案或資料夾
// 檔案
Var File = function(name) {
return {
add: function() {
console.log('can not add child file');
},
execute: function() {
console.log('複製檔案: ' + name);
},
scan: function() {
console.log('掃描檔案: ' + name);
}
}
};
// 資料夾
var Folder =function(name) {
var cache = [];
return {
add: function(file) {
cache.push(file);
},
execute: function() {
console.log('複製資料夾: ' + name);
},
scan: function() {
console.log('掃描資料夾: ' + name);
}
}
};
// 建立資料夾和檔案物件,讓他們組成一棵樹
var folder = new Folder( '學習資料' );
var folder1 = new Folder( 'JavaScript' );
var folder2 = new Folder ( 'jQuery' );
var file1 = new File( 'JavaScript 設計模式與開發實踐' );
var file2 = new File( '精通 jQuery' );
var file3 = new File( '重構與模式' );
folder1.add( file1 );
folder2.add( file2 );
folder.add( folder1 );
folder.add( folder2 );
folder.add( file3 );
複製程式碼
此時在我們的目錄中已經有了一顆完整的樹了,那麼我希望將folder資料夾複製到另一個位置,會有掃描檔案,和複製檔案的過程:
// 將鍵盤按下CTRL + C的時候,註冊事件執行
foler.scan();
// 將鍵盤按下CTRL + V的時候,註冊事件執行
folder.execute();
複製程式碼
當我們希望新增新的節點的時候,我們可以直接增加新增件節點到folder中,我們改變了樹的結構,增加了新的資料,卻不用修改任何一句原有的程式碼,這是符合開放-封閉原則的。
var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入淺出 Node.js' );
folder3.add( file4 );
var file5 = new File( 'JavaScript 語言精髓與程式設計實踐' );
// 新增到folder結構樹中,修改了整體結構
folder.add( folder3 );
folder.add( file5 );
複製程式碼
6 組合模式小結
- 組合模式不是父子關係(繼承)
組合模式是一種HAS-A(聚合)
關係,不是IS-A
。組合物件包含一組葉物件,但是葉物件並不是Composite(組合)的子類。組合物件最終會將請求委託給所有的葉子物件,它們能夠合作關鍵按是擁有相同的介面。 - 對葉物件的操作一致
組合物件與葉物件擁有相同的介面外,對同一組葉子物件的操作必須是一致的。例如上面的我要掃描檔案
,不能在組合物件中出現呼叫複製檔案
的介面。 - 雙向對映關係
例如公司給員工發放福利卡。公司給各個部分發放,部分給員工發放。組合模式就很適合,但是如果員工A是一個架構師,既屬於A部門也屬於B部門,那麼很有可能被髮放兩次。就不適合了。 - 用職責鏈模式提高組合模式效能
組合模式中,如果樹結構比較複雜,節點較多,那麼遍歷過程中效能就不好了。我們可以藉助職責鏈:手動設定鏈條,將組合物件與子節點之間效能職責鏈。讓請求從組合象到子物件,或從子物件到父物件都能傳遞。 - 組合模式缺點:因為組合的內容都是程式碼中已存在的,會讓系統中的物件看起來和其他的物件差不多,只有在執行的時候才能顯示出來。如果組合模式建立太多物件,可能讓系統負擔不起。
利用職責鏈思想在子節點保持對父節點的引用。可以從自己子節點往父節點冒泡。
var File = function(name) {
this.parent = undefined;
this.name = name;
};
File.prototype.add = function(file) {
console.log('can not add child file');
}
File.prototype.remove = function() {
// 根節點或則遊離無組織的節點
if (!this.parent) {
return;
}
var self = this;
this.parent.cache = this.parent.cache.filter(function(f) {
return f != self;
});
}
// 資料夾
var Folder = function(name) {
this.parent = undefined;
this.cache = [];
this.name = name;
};
Folder.prototype.add = function(file) {
file.parent = this; // 設定父物件
this.cache.push(file);
}
Folder.prototype.remove = function() {
// 根節點或則遊離無組織的節點
if (!this.parent) {
return;
}
console.log(' 移除資料夾' + this.name, this.parent.cache );
var self = this;
this.parent.cache = this.parent.cache.filter(function(f) {
return f != self;
});
console.log(' 移除資料夾' + this.name, this.parent.cache );
}
// 移除資料夾
var folder = new Folder( '學習資料' );
var folder1 = new Folder( 'JavaScript' );
var file1 = new File ( '深入淺出 Node.js' );
folder1.add( new File( 'JavaScript 設計模式與開發實踐' ) );
folder.add( folder1 );
folder.add( file1 );
folder1.remove(); //移除資料夾
複製程式碼