js之組合模式

在命令模式學習完的基礎上:

更強大的宏命令

<!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>

一些值得注意的地方

  1. 組合模式不是父子關係
  2. 對葉對象操作的一致性

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 語句來分別處理它們。組合對象和葉對象會各自做自己正確的事情,這是組合模式最重要的能力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章