聽飛狐聊JavaScript設計模式系列12

本回內容介紹

上一回,聊了橋接模式,作了一道計算題;介一回,聊組合模式(Composite),官方描述組合模式將對象組合成樹形結構以表示「部分-總體」的層次結構,組合模式使得用戶對單個對象和組合對象的使用具備一致性。javascript

組合模式特性

這裏我理了一下,就組合模式的特性而言:
1,組合模式把對象分爲組合對象和葉子對象兩種。
2,組合對象和葉子對象實現同一批操做。
3,對組合對象執行的操做能夠向下傳遞到葉子節點進行操做。
這樣作帶來的好處
1,解耦,弱化類與類之間的耦合,一樣的方法獲得抽離處理組合對象和葉子對象;
2,把對象組合成屬性結構的對象。
這個也是我在網上看了不少描述後作的總結。這裏先看一下,而後看例子,看完例子再來看總結,應該會更有心得,來吧,開始咯。html

1.組合模式

這裏須要用到以前寫過的接口類,不清楚的童鞋看看前面聊過的系列05,這裏模擬一個導航菜單,如京東的一級導航,二級導航,三級導航,代碼以下:java

var d = document;
// 定義組合接口
var CompositeInterface = new Interface('CompositeInterface',['addChild','getChild','href']);
// 定義葉子接口
var LeafInterface = new Interface('LeafInterface',['href']);
// 定義組合類,並定義名字,類型,子集
var Composite = function(name){
    this.name = name;
    this.type = 'Composite';
    this.children = [];
}
// 組合類的方法實現
Composite.prototype = {
    // 以前說過不少次的,還原指針
    constructor:Composite,
    // 添加子集
    addChild:function(child){
        this.children.push(child);
        return this;
    },
    // 獲取子集,這裏是組合模式的關鍵
    getChild:function(name){
        // 定義一個結果數組
        var el = [];
        // 添加葉子對象的方法
        var addLeaf = function(item){
            // 判斷傳入的類型爲組合對象的狀況
            if(item.type==="Composite"){
                // 若是爲組合對象說明還有下一級,則遞歸,還記得forEach函數吧,系列01講過的,不清楚的回過頭去看看再回憶一下,這裏的arguments.callee是指向函數自己的指針
                item.children.forEach(arguments.callee);
            // 判斷若是爲葉子對象,則直接添加到結果集
            }else if(item.type==="Leaf"){
                el.push(item);
            }        
        };
        // 判斷傳入的導航節點是否存在,而且是否等於當前的節點
        if(name&&this.name!==name){
            // 遍歷沒什麼好說的
            this.children.forEach(function(item){
                // 判斷傳入節點爲當前節點而且爲組合對象則遞歸
                if(item.name === name&&item.type === 'Composite'){
                    item.children.forEach(addLeaf);
                }
                // 傳入的節點非當前節點而且是組合對象則遞歸
                if(item.name !== name&&item.type === 'Composite'){
                    item.children.forEach(arguments.callee);
                }
                // 傳入的類型若是是葉子對象,正好是調用的節點,則直接添加到結果集
                if(item.name === name&&item.type === 'Leaf'){
                    el.push(item);
                }
            });
        // 這裏是不傳參,或者不等於當前節點的狀況
        }else{
            // 這裏的遞歸同上
            this.children.forEach(addLeaf);
        }
        return el;
    },
    // 跳轉的方法
    href:function(name){
        // 獲取葉子對象
        var leaves = this.getChild(name);
        // 遍歷並執行葉子對象的跳轉
        for(var i=0;i<leaves.length;i++){
            leaves[i].href();
        }
    }
};
// 定義葉子類,並定義名字,類型,子集
var Leaf = function(name){
    this.name = name;
    this.type = 'Leaf';        
}
// 定義葉子類的方法
Leaf.prototype = {
    constructor:Leaf,
    // 這裏就簡單寫入名字當模擬跳轉了
    href:function(name){
        d.write(this.name+"</br>");
    }
};

代碼量太多,仍是把測試部分代碼分開,以下:node

// 如下是測試的代碼
var n1 = new Leaf("三級導航1");
var n2 = new Leaf("三級導航2");
var n3 = new Leaf("三級導航3");
var n4 = new Leaf("三級導航4");
var n5 = new Leaf("三級導航5");
var n6 = new Leaf("三級導航6");
// 寫一個二級導航1,把前三個放入二級導航1
var nav1 = new Composite("二級導航1");
nav1.addChild(n1);
nav1.addChild(n2);
nav1.addChild(n3);
// 寫一個二級導航2,把後三個放入二級導航2
var nav2 = new Composite("二級導航2");
nav2.addChild(n4);
nav2.addChild(n5);
nav2.addChild(n6);
// 寫一個一級導航,把兩個二級導航放入一級導航
var nav = new Composite();
nav.addChild(nav1);
nav.addChild(nav2);
// 這裏不傳則返回所有
nav.href("二級導航1");    // 返回三級導航1,三級導航2,三級導航3

做爲第一個例子,爲了便於你們理解,我基本把註釋都寫完了,把一下葉子對象的方法省去了,只寫了一個方法,更直觀方便理解。下一個例子用一個圖片庫來演示,走你。編程

2. 組合模式之圖片庫

圖片庫能夠有選擇地隱藏或顯示圖片庫的所有或某一部分(單獨的或是部分的)。同上面一個例子同樣,一個組合類作庫、一個葉子類則是圖片自己,以下:數組

<div id="main"></div>
var d = document;
// 檢查組合對象Composite應該具有的方法
var Composite     = new Interface('Composite',['add','remove','getChild']);
// 檢查組合對象GalleryItem應該具有的方法
var GalleryItem = new Interface('GalleryItem',['hide','show']); 
// 實現Composite,GalleryItem組合對象類            
var DynamicGallery = function(id){             
    // 定義子集
    this.children = [];
    // 建立dom元素
    this.el       = d.createElement('div');
    // 這個id跟上面個例子的name是同樣的,傳入名
    this.el.id       = id;
    // 這個className跟上面例子的type是同樣的,區分層級
    this.el.className = 'imageLib';
}
// 組合類的方法實現
DynamicGallery.prototype = {
    constructor:DynamicGallery,
    //  實現Composite組合對象接口
    add : function(child){
        // 檢測接口
        Interface.ensureImplements(child,Composite,GalleryItem);
        // 添加元素
        this.children.push(child);
        // 添加元素到末尾的方法appendChild,不清楚的童鞋在網上搜搜哈
        this.el.appendChild(child.getElement());
    },
    // 刪除節點
    remove : function(child){
        for(var node, i = 0; node = this.getChild(i); i++){
            // 這裏判斷是否存在,存在則刪除
            if(node == child){
                // 這裏用數組的方法splice,不清楚的童鞋網上搜搜,比較有意思的一個方法
                this.children.splice(i,1);
                break;
            }
        }
        // dom元素的刪除方法removeChild,不清楚的童鞋網上搜一下吧,嘿嘿~
        this.el.removeChild(child.getElement());
    },
    getChild : function(i){
        return this.children[i];
    },
    //  實現葉子對象
    hide : function(){
        for(var node, i = 0; node = this.getChild(i); i++){
            node.hide();
        }
        this.el.style.display = 'none';
    },
    //  實現葉子對象
    show : function(){
        this.el.style.display = 'block';
        for(var node, i = 0; node = this.getChild(i); i++){
            node.show();
        }
    },
    //  獲取當前的節點
    getElement : function(){
        return this.el;
    }
}    
//  葉子類
var GalleryImage = function(src){
    this.el = document.createElement('img');
    this.el.className = 'imageLeaf';
    this.el.src = src;
}
// 葉子類的方法實現
GalleryImage.prototype = {
    constructor:GalleryImage,
    // 這裏的方法都是葉子對象的,已是葉子對象了,處於最底層的,沒有下一級了。上一個例子沒有寫,是由於儘可能少寫代碼便於理解,這裏咱們不定義具體的實現,直接拋出就行了
    add : function(){
        throw new Error('This is not a instance!');
    },
    remove : function(){
        throw new Error('This is not a instance!');
    },
    getChild : function(id){
        // 判斷是不是當前元素,是則返回
        if(this.id = id){
            return this;
        }
        return null;
    },
 
    // 隱藏
    hide : function(){
        this.el.style.display = 'none';
    },
    // 顯示
    show : function(){
        this.el.style.display = 'block';
    },
    getElement : function(){
        return this.el;
    }
}

測試部分,代碼以下:app

window.onload = function(){
    // 從這開始是測試部分,組合類one,用one來表示層級最高吧
    var one = new DynamicGallery('one');
    // 這裏能夠循環多張圖片來測試,隨便搜點兒圖片作測試
    var item1 = new GalleryImage('./1.jpg');
    var item2 = new GalleryImage('./2.jpg');
    var item3 = new GalleryImage('./3.jpg');
    // 添加葉子對象到頂級組合類one
    one.add(item1);
    one.add(item2);
    one.add(item3);
    // 組合類two,層級次於one
    two = new DynamicGallery('two');
    // 一樣這裏也能夠循環多張圖片來測試
    var item4 = new GalleryImage('./4.jpg');
    var item5 = new GalleryImage('./5.jpg');
    var item6 = new GalleryImage('./6.jpg');
    two.add(item4);
    two.add(item5);
    two.add(item6);
    // 鏈式操做,後面會聊到
    d.getElementById('main').appendChild(one.getElement());
    one.add(two);
    one.show();
    // 這裏寫show,two裏的圖片則顯示
    two.hide();
}

這個例子在網上不少,這裏我改了下代碼,使組合對象和葉子對象更直觀,讓這兩個類來管理圖片庫,代碼能夠直接copy運行。dom

裝逼圖
裝個逼咯。雙12大超市小鋪子都在搞活動,又是一陣買買買~~ide

這一回聊的組合模式,對於剛學JS面向對象的童鞋,很有難度,不過沒關係,困難像彈簧,你懂的呃(的呃要快速連讀^_^)~
下面的內容,來聊聊遞歸,由於這回的組合模式用到了遞歸,恰好能夠學習一下加深印象。函數

遞歸

官方概述程序調用自身的編程技巧稱爲遞歸( recursion)。

// 經典的累加,start簡寫s,end簡寫e,開始和結束的數字
function add(s,e){
    // 初始化遍歷爲number類型,默認值0
    var num = 0;
    // 先加第一項
    num += s;
    // 判斷首項小於末項則執行
    if(s<e){
        //這裏是關鍵遞歸關鍵啦,argument.callee是指向函數自身的指針
        //等同於num += add(s+1,e); 
        num += arguments.callee(s+1,e);
    }
    return num;
}
alert(add(1,100)) // 5050

這裏之因此用arguments.callee,好處就在於改變函數名的時候,不用再去該內部的代碼,防止出錯。

這一回,主要聊了組合模式,遞歸,其中組合模式還回憶了以前聊過的接口類,數組新特性forEach等,這回比較抽象,須要多理解~~
下一回,聊一聊狀態模式。

看完點個贊,推薦推薦咯,人氣+++,動力才能+++吖,嘿嘿~~


注:此係飛狐原創,轉載請註明出處

相關文章
相關標籤/搜索