在命令模式學習完的基礎上:
更強大的宏命令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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 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>
</body>
</html>
從這個例子中可以看到,基本對象可以被組合成更復雜的組合對象,組合對象又可以被組合,這樣不斷遞歸下去,這棵樹的結構可以支持任意多的複雜度。在樹最終被構造完成之後,讓整顆
樹最終運轉起來的步驟非常簡單,只需要調用最上層對象的 execute 方法。每當對最上層的對象進行一次請求時,實際上是在對整個樹進行深度優先的搜索,而創建組合對象的程序員並不關心這些內在的細節,往這棵樹裏面添加一些新的節點對象是非常容易的事情。
組合模式的例子——掃描文件夾
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="button">按我</button>
<script>
/******************************* Folder ******************************/
var Folder = function( name ){
this.name = name;
this.files = [];
};
Folder.prototype.add = function( file ){
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '開始掃描文件夾: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
/******************************* File ******************************/
var File = function( name ){
this.name = name;
};
File.prototype.add = function(){
throw new Error( '文件下面不能再添加文件' );
};
File.prototype.scan = function(){
console.log( '開始掃描文件: ' + this.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.scan();
</script>
</body>
</html>
一些值得注意的地方
- 組合模式不是父子關係
- 對葉對象操作的一致性
level02:引用父對象
在 上個例子中,組合對象保存了它下面的子節點的引用,這是組合模式的特點,此時樹結構是從上至下的。但有時候我們需要在子節點上保持對父節點的引用,比如在組合模式中使用職責鏈時,有可能需要讓請求從子節點往父節點上冒泡傳遞。還有當我們刪除某個文件的時候,實際上是從這個文件所在的上層文件夾中刪除該文件的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var Folder = function( name ){
this.name = name;
this.parent = null; //增加 this.parent 屬性
this.files = [];
};
Folder.prototype.add = function( file ){
file.parent = this; //設置父對象
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '開始掃描文件夾: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
Folder.prototype.remove = function(){
if ( !this.parent ){ //根節點或者樹外的遊離節點
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 );
}
}
};
var File = function( name ){
this.name = name;
this.parent = null;
};
File.prototype.add = function(){
throw new Error( '不能添加在文件下面' );
};
File.prototype.scan = function(){
console.log( '開始掃描文件: ' + this.name );
};
File.prototype.remove = function(){
if ( !this.parent ){ //根節點或者樹外的遊離節點
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 );
}
}
};
var folder = new Folder( '學習資料' );
var folder1 = new Folder( 'JavaScript' );
var file1 = new Folder ( '深入淺出 Node.js' );
folder1.add( new File( 'JavaScript 設計模式與開發實踐' ) );
folder.add( folder1 );
folder.add( file1 );
file1.remove();
folder1.remove(); //移除文件夾
folder.scan();
</script>
</body>
</html>
何時使用組合模式
組合模式如果運用得當,可以大大簡化客戶的代碼。一般來說,組合模式適用於以下這兩種情況。
表示對象的部分整體層次結構。組合模式可以方便地構造一棵樹來表示對象的部分整體結構。特別是我們在開發期間不確定這棵樹到底存在多少層次的時候。在樹的構造最終完成之後,只需要通過請求樹的最頂層對象,便能對整棵樹做統一的操作。在組合模式中增加和刪除樹的節點非常方便,並且符合開放封閉原則。
客戶希望統一對待樹中的所有對象。組合模式使客戶可以忽略組合對象和葉對象的區別,客戶在面對這棵樹的時候,不用關心當前正在處理的對象是組合對象還是葉對象,也就不用寫一堆 if 、 else 語句來分別處理它們。組合對象和葉對象會各自做自己正確的事情,這是組合模式最重要的能力。