《JavaScript設計模式與開發實踐》模式篇(7)—— 組合模式

嗨呀豆豆呢發表於2019-03-01

組合模式將物件組合成樹形結構,以表示“部分-整體”的層次結構。 除了用來表示樹形結構之外,組合模式的另一個好處是通過物件的多型性表現,使得使用者對單個物件和組合物件的使用具有一致性

以命令模式中的巨集命令程式碼為例,巨集命令物件包含了一組具體的子命令物件,不管是巨集命令物件,還是子命令物件,都有一個execute方法負責執行命令。巨集命令中包含了一組子命令,它們組成了一個樹形結構,這裡是一棵結構非常簡單的樹

巨集命令樹結構

在組合模式中,請求在樹中傳遞的過程總是遵循一種邏輯。請求從樹最頂端的物件往下傳遞,如果當前處理請求的物件是葉物件(普通子命令),葉物件自身會對請求作出相應的處理;如果當前處理請求的物件是組合物件(巨集命令), 組合物件則會遍歷它屬下的子節點,將請求繼續傳遞給這些子節點。

組合模式請求傳遞方式

  • 組合模式下的巨集命令 目前的萬能遙控器,包含了關門、開電腦、登入 QQ 這 3 個命令。現在我們需要一個“超級萬能遙控器”,可以控制家裡所有的電器,這個遙控器擁有以下功能
    • 開啟空調
    • 開啟電視和音響
    • 關門、開電腦、登入 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 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 );
複製程式碼

從這個例子中可以看到,基本物件可以被組合成更復雜的組合物件,組合物件又可以被組合, 這樣不斷遞迴下去,這棵樹的結構可以支援任意多的複雜度。在樹最終被構造完成之後,讓整顆 樹最終運轉起來的步驟非常簡單,只需要呼叫最上層物件的 execute 方法。每當對最上層的物件 進行一次請求時,實際上是在對整個樹進行深度優先的搜尋,而建立組合物件的程式設計師並不關心這些內在的細節,往這棵樹裡面新增一些新的節點物件是非常容易的事情。

應用場景 —— 掃描資料夾

資料夾和檔案之間的關係,非常適合用組合模式來描述。資料夾裡既可以包含檔案,又可以 包含其他資料夾,最終可能組合成一棵樹 當使用用防毒軟體掃描該資料夾時,往往不會關心裡面有多少檔案和子資料夾,組合模式使得我們只需要操作最外層的資料夾進行掃描。

程式碼實現

/* 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++ ]; ){
        files 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 );
};
/*建立一些資料夾和檔案物件, 並且讓它們組合成一棵樹,這棵樹就是我們 F 盤裡的 現有檔案目錄結構*/
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 );

/*現在的需求是把行動硬碟裡的檔案和資料夾都複製到這棵樹中,假設我們已經得到了這些檔案物件*/
var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入淺出 Node.js' ); 
folder3.add( file4 );
var file5 = new File( 'JavaScript 語言精髓與程式設計實踐' );

/*接下來就是把這些檔案都新增到原有的樹中*/
folder.add( folder3 ); 
folder.add( file5 );
複製程式碼

小結

組合模式可以讓我們使用樹形方式創 建物件的結構。我們可以把相同的操作應用在組合物件和單個物件上。在大多數情況下,我們都 可以忽略掉組合物件和單個物件之間的差別,從而用一致的方式來處理它們。 然而,組合模式並不是完美的,它可能會產生一個這樣的系統:系統中的每個物件看起來都 與其他物件差不多。它們的區別只有在執行的時候會才會顯現出來,這會使程式碼難以理解。此外,如果通過組合模式建立了太多的物件,那麼這些物件可能會讓系統負擔不起。

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章