我們平時開發過程中,一定會遇到這種情況:同時處理簡單對象和由簡單對象組成的復雜對象,這些簡單對象和復雜對象會組合成樹形結構,在客戶端對其處理的時候要保持一致性。比如電商網站中的產品訂單,每一張產品訂單可能有多個子訂單組合,比如操作系統的文件夾,每個文件夾有多個子文件夾或文件,我們作為用戶對其進行復制,刪除等操作時,不管是文件夾還是文件,對我們操作者來說是一樣的。在這種場景下,就非常適合使用組合模式來實現。
基本知識
組合模式:將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性。
組合模式主要有三個角色:
(1)抽象組件(Component):抽象類,主要定義了參與組合的對象的公共接口
(2)子對象(Leaf):組成組合對象的最基本對象
(3)組合對象(Composite):由子對象組合起來的復雜對象
理解組合模式的關鍵是要理解組合模式對單個對象和組合對象使用的一致性,我們接下來說說組合模式的實現加深理解。
組合模式算是為在頁面動態創建UI量身定做的,你可以只使用一條命=命令為許多對象初始化一些復雜的或者遞歸的操作.組合模式提供了兩個有點:
(1)允許你將一組對象當成特定的對象.組合對象(A composite)和組成它的子對象實現相同的操作.對組合對象執行某一個操作將會使該對象下的所有子對象執行相同的操作.因此你不僅可以無縫的替換單個對象為一組對象集合,反過來也一樣.這些獨立的對象之間即所謂松散耦合的.
(2)組合模式會將子對象集組合成樹結構並且允許遍歷整個樹.這樣可以隱藏內部實現並且允許你以任意的方式組織子對象.這個對象(組合對象)的任何代碼將不會依賴內部子對象的實現.
組合模式的實現
(1)最簡單的組合模式
HTML文檔的DOM結構就是天生的樹形結構,最基本的元素醉成DOM樹,最終形成DOM文檔,非常適用適用組合模式。
我們常用的jQuery類庫,其中組合模式的應用更是頻繁,例如經常有下列代碼實現:
$(".test").addClass("noTest").remove("test");
這句簡單的代碼就是獲取class包含test的元素,然後進行addClass和removeClass處理,其中不論$(“.test”)是一個元素,還是多個元素,最終都是通過統一的addClass和removeClass接口進行調用。
我們簡單模擬一下addClass的實現:
var addClass = function (eles, className) { if (eles instanceof NodeList) { for (var i = 0, length = eles.length; i < length; i++) { eles[i].nodeType === 1 && (eles[i].className += (' ' + className + ' ')); } } else if (eles instanceof Node) { eles.nodeType === 1 && (eles.className += (' ' + className + ' ')); } else { throw "eles is not a html node"; } } addClass(document.getElementById("div3"), "test"); addClass(document.querySelectorAll(".div"), "test");
這段代碼簡單的模擬了addClass的實現(暫不考慮兼容性和通用性),很簡單地先判斷節點類型,然後根據不同類型添加className。對於NodeList或者是Node來說,客戶端調用都是同樣的使用了addClass這個接口,這個就是組合模式的最基本的思想,使部分和整體的使用具有一致性。
(2)典型的例子
前面我們提到一個典型的例子:產品訂單包含多個產品子訂單,多個產品子訂單組成一個復雜的產品訂單。由於Javascript語言的特性,我們將組合模式的三個角色簡化成2個角色:
(1)子對象:在這個例子中,子對象就是產品子訂單
(2)組合對象:這裡就是產品的總訂單
假設我們開發一個旅游產品網站,其中包含機票和酒店兩種子產品,我們定義了子對象如下:
function FlightOrder() { } FlightOrder.prototyp.create = function () { console.log("flight order created"); } function HotelOrder() { } HotelOrder.prototype.create = function () { console.log("hotel order created"); }
上面的代碼定義了兩個類:機票訂單類和酒店訂單類,每個類都有各自的訂單創建方法。
接下來我們創建一個總訂單類:
function TotalOrders() { this.orderList = []; } TotalOrders.prototype.addOrder = function (order) { this.orderList.push(order); } TotalOrders.prototype.create = function (order) { for (var i = 0, length = this.orderList.length; i < length; i++) { this.orderList[i].create(); } }
這個對象主要有3個成員:訂單列表,添加訂單的方法,創建訂單的方法。
在客戶端使用的時候如下:
var flight = new FlightOrder(); flight.create(); var orders = new TotalOrders(); orders.addOrder(new FlightOrder()); orders.addOrder(new HotelOrder()); orders.create();
客戶端調用展示了兩種方式,一種是單一的創建機票訂單,一種是創建多張訂單,但最終都是通過create方法進行創建,這就是一個很典型的組合模式的應用場景。
總結
組合模式並不難理解,它主要解決的是單一對象和組合對象在使用方式上的一致性問題。如果對象具有明顯的層次結構並且想要統一地使用它們,這就非常適合使用組合模式。在Web開發中,這種層次結構非常常見,很適合使用組合模式,尤其是對於JS來說,不用拘泥於傳統面向對象語言的形式,靈活地利用JS語言的特性,達到對部分和整體使用的一致性。
(1)使用組合模式的場景
在遇到下面兩種情況的時候才使用組合模式
A.含有某種層級結構的對象集合(具體結構在開發過程中無法確定)
B.希望對這些對象或者其中的某些對象執行某種操作
(2)組合模式的缺點
因為組合對象的任何操作都會對所有的子對象調用同樣的操作,所以當組合的結構很大時會有性能問題。還有就是使用組合模式封裝HTML時要選擇合適的標簽,比如table就不能用於組合模式,葉子節點不明顯