上一回,聊了橋接模式,作了一道計算題;介一回,聊組合模式(Composite),官方描述組合模式將對象組合成樹形結構以表示「部分-總體」的層次結構,組合模式使得用戶對單個對象和組合對象的使用具備一致性。javascript
這裏我理了一下,就組合模式的特性而言:
1,組合模式把對象分爲組合對象和葉子對象兩種。
2,組合對象和葉子對象實現同一批操做。
3,對組合對象執行的操做能夠向下傳遞到葉子節點進行操做。
這樣作帶來的好處:
1,解耦,弱化類與類之間的耦合,一樣的方法獲得抽離處理組合對象和葉子對象;
2,把對象組合成屬性結構的對象。
這個也是我在網上看了不少描述後作的總結。這裏先看一下,而後看例子,看完例子再來看總結,應該會更有心得,來吧,開始咯。html
這裏須要用到以前寫過的接口類,不清楚的童鞋看看前面聊過的系列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
做爲第一個例子,爲了便於你們理解,我基本把註釋都寫完了,把一下葉子對象的方法省去了,只寫了一個方法,更直觀方便理解。下一個例子用一個圖片庫來演示,走你。編程
圖片庫能夠有選擇地隱藏或顯示圖片庫的所有或某一部分(單獨的或是部分的)。同上面一個例子同樣,一個組合類作庫、一個葉子類則是圖片自己,以下:數組
<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等,這回比較抽象,須要多理解~~
下一回,聊一聊狀態模式。
看完點個贊,推薦推薦咯,人氣+++,動力才能+++吖,嘿嘿~~
注:此係飛狐原創,轉載請註明出處