設(shè)計模式之組合模式

組合模式

組合模式,將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),組合模式使得用戶對單個對象和組合對象的使用具有一致性。掌握組合模式的重點是要理解清楚 “部分/整體” 還有 ”單個對象“ 與 "組合對象" 的含義。
組合模式可以讓客戶端像修改配置文件一樣簡單的完成本來需要流程控制語句來完成的功能。
經(jīng)典案例:系統(tǒng)目錄結(jié)構(gòu),網(wǎng)站導(dǎo)航結(jié)構(gòu)等。

模式作用:

  1. 你想表示對象的部分-整體層次結(jié)構(gòu)時
  2. 你希望用戶忽略組合對象和單個對象的不同,用戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對象(方法)

注意事項:

  1. 該模式經(jīng)常和裝飾者模式一起使用,它們通常有一個公共的父類(也就是原型),因此裝飾必須支持具有add,remove,getChild操作的component接口

例子:

文件夾和文件之間的關(guān)系,非常適合用組合模式來描述.文件夾里既可以包含文件,又可以包含其他文件夾,最終可能組合成一棵樹,組合模式在文件夾的應(yīng)用中有一下兩層好處.
例如,我在同事的移動硬盤里找到了一些電子書,想把它們復(fù)制到F盤中的學(xué)習(xí)資料文件夾.在復(fù)制這些電子書的時候,我并不需要考慮這批文件的類型,不管它們是單獨的電子書還是被放在了文件夾中.組合模式讓Ctrl+V,Ctrl+C成為了一個統(tǒng)一的操作.
當我用殺毒軟件掃描該文件夾時,往往不會關(guān)心里面有多少文件和子文件夾,組合模式使得我們只需要操作最外層的文件夾進行掃描
現(xiàn)在我們來編寫代碼,首先分別定義好文件夾Folder和文件File這兩個類.見如下代碼:

/***********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=this.files[i];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);
}

接下來創(chuàng)建一些文件夾和文件對象,并且讓它們組合成一棵樹,這棵樹就是我們F盤里的現(xiàn)有文件目錄結(jié)構(gòu):

var folder=new Folder("學(xué)習(xí)資料");
var folder1=new Folder("JavaScript");
var folder2=new Folder("JQuery");
var file1=new File("JavaScript設(shè)計模式與開發(fā)實踐")
var file2=new File("精通JQuery");
var file3=new File("重構(gòu)與模式");
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);

現(xiàn)在的需求是把移動硬盤里的文件和文件夾都復(fù)制到這棵樹中,假設(shè)我們已經(jīng)得到了這些文件對象:

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);

通過這個例子,我們再次看到客戶是如何同等對待組合對象和葉對象.在添加一批文件的操作過程中,客戶不用分辨它們到底是文件還是文件夾.新增加的文件和文件夾能夠很容易地添加到原來的樹結(jié)構(gòu)中,和樹里已有的對象一起工作
我們改變了樹的結(jié)構(gòu),添加了新的數(shù)據(jù),卻不用修改任何一句原有的代碼,這是符合開放-封閉原則的.
運用了組合模式之后,掃描整個文件夾的操作也是輕而易舉的,我們只需要操作樹的最頂端對象

folder.scan()

執(zhí)行結(jié)果如下如所示:


一些值得注意的地方

  1. 組合模式不是父子關(guān)系
    組合模式的樹形結(jié)構(gòu)容易讓人誤以為組合對象和葉對象是父子關(guān)系,這是不正確的.
    組合模式是一種HAS-A(聚合)的關(guān)系,而不是IS-A.組合對象包含一組葉對象,但Leaf并不是composite的子類.組合對象把請求委托給它所包含的所有葉對象,它們能夠合作的關(guān)鍵是擁有相同的接口.
  2. 和葉對象操作的一致性
    組合模式除了要求組合對象和葉對象擁有相同的接口之外,還有一個必要條件,就是對一組葉對象的操作必須具有一致性.
    比如公司要給全體員工發(fā)放元旦的過節(jié)費1000塊,這個場景可以運用組合模式,但如果公司給今天過生日的員工發(fā)送一封生日祝福的郵件,組合模式在這里就沒有用武之地了,除非先把今天過生日的員工挑選出來.只有用一致的方式對待列表中的每個葉對象的時候,才適合使用組合模式
  3. 雙向映射關(guān)系
    發(fā)放過節(jié)費的通知步驟是從公司到各個部門,再到各個小組,最后到每個員工的郵箱里.這本身是個組合模式的好例子,但要考慮的一種情況是,也許某些員工屬于多個組織架構(gòu).比如某位架構(gòu)師既隸屬于開發(fā)組,又隸屬于架構(gòu)組,對象之間的關(guān)系并不是嚴格意義上的層次結(jié)構(gòu),在這種情況下,是不適合使用組合模式的額,該架構(gòu)師很可能會收到兩份過節(jié)費.
    這種復(fù)合情況下我們必須給父節(jié)點和子節(jié)點建立雙向映射關(guān)系,一個簡單的方法是給小組和員工對象都增加集合來保存對方的引用.但是這種相互間的引用相當復(fù)雜,而且對象之間產(chǎn)生了過多的耦合性,修改或者刪除一個對象都變得困難,此時我們可以引入中介者模式來管理這些對象
  4. 用職責鏈模式來提高組合模式性能
    在組合模式中,如果樹的結(jié)構(gòu)比較復(fù)雜,節(jié)點數(shù)量很多,在遍歷樹的過程中,性能方面也許表現(xiàn)得不夠理想.有時候我們確實可以借助一些技巧,在實際操作中避免遍歷整棵樹,有一種現(xiàn)成的方案是借助職責鏈模式.職責鏈模式一般需要我們手動去設(shè)置鏈條,但在組合模式中,父對象和自對象之間實際上形成了天然的職責鏈.讓請求順著鏈條從父對象往子對象傳遞,或者是反過來從子對象往父對象傳遞,直到遇到可以處理該請求的對象為止,這也是職責鏈模式的經(jīng)典運用場景之一.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容