組合模式:又叫 「部分總體」 模式,將對象組合成樹形結構,以表示 「部分-總體」 的層次結構。經過對象的多態性表現,使得用戶對單個對象和組合對象的使用具備一致性。
生活小栗子:文件目錄,DOM 文檔樹javascript
樹對象和葉對象接口統一,樹對象增長一個緩存數組,存儲葉對象。執行樹對象方法時,將請求傳遞給其下葉對象執行。java
// 樹對象 - 文件目錄 class CFolder { constructor(name) { this.name = name; this.files = []; } add(file) { this.files.push(file); } scan() { for (let file of this.files) { file.scan(); } } } // 葉對象 - 文件 class CFile { constructor(name) { this.name = name; } add(file) { throw new Error('文件下面不能再添加文件'); } scan() { console.log(`開始掃描文件:${this.name}`); } } let mediaFolder = new CFolder('娛樂'); let movieFolder = new CFolder('電影'); let musicFolder = new CFolder('音樂'); let file1 = new CFile('鋼鐵俠.mp4'); let file2 = new CFile('再談記憶.mp3'); movieFolder.add(file1); musicFolder.add(file2); mediaFolder.add(movieFolder); mediaFolder.add(musicFolder); mediaFolder.scan(); /* 輸出: 開始掃描文件:鋼鐵俠.mp4 開始掃描文件:再談記憶.mp3 */
CFolder
與 CFile
接口保持一致。執行 scan()
時,若發現是樹對象,則繼續遍歷其下的葉對象,執行 scan()
。git
JavaScript 不一樣於其它靜態編程語言,實現組合模式的難點是保持樹對象與葉對象之間接口保持統一,可藉助 TypeScript 定製接口規範,實現類型約束。github
// 定義接口規範 interface Compose { name: string, add(file: CFile): void, scan(): void } // 樹對象 - 文件目錄 class CFolder implements Compose { fileList = []; name: string; constructor(name: string) { this.name = name; } add(file: CFile) { this.fileList.push(file); } scan() { for (let file of this.fileList) { file.scan(); } } } // 葉對象 - 文件 class CFile implements Compose { name: string; constructor(name: string) { this.name = name; } add(file: CFile) { throw new Error('文件下面不能再添加文件'); } scan() { console.log(`開始掃描:${this.name}`) } } let mediaFolder = new CFolder('娛樂'); let movieFolder = new CFolder('電影'); let musicFolder = new CFolder('音樂'); let file1 = new CFile('鋼鐵俠.mp4'); let file2 = new CFile('再談記憶.mp3'); movieFolder.add(file1); musicFolder.add(file2); mediaFolder.add(movieFolder); mediaFolder.add(musicFolder); mediaFolder.scan(); /* 輸出: 開始掃描文件:鋼鐵俠.mp4 開始掃描文件:再談記憶.mp3 */
組合模式的透明性,指的是樹葉對象接口保持統一,外部調用時無需區分。可是這會帶來一些問題,如上述文件目錄的例子,文件(葉對象)下不可再添加文件,所以需在文件類的 add()
方法中拋出異常,以做提醒。typescript
組合模式的樹型結構是一種 HAS-A(聚合)的關係,而不是 IS-A 。樹葉對象可以合做的關鍵,是它們對外保持統一接口,而不是葉對象繼承樹對象的屬性方法,二者之間不是父子關係。編程
葉對象除了與樹對象接口一致外,操做也必須保持一致性。一片葉子只能生在一顆樹上。調用頂層對象時,每一個葉對象只能接收一次請求,一個葉對象不能從屬多個樹對象。設計模式
請求傳遞由樹向葉傳遞,若是想逆轉傳遞過程,需在葉對象中保留對樹對象的引用,冒泡傳遞給樹對象處理。數組
調用對象的接口方法時,若是該對象是樹對象,則會將請求傳遞給葉對象,由葉對象執行方法,以此類推。不一樣於迭代器模式,迭代器模式遍歷並不會作請求傳導。緩存
優勢:安全
缺點
參考文章
本文首發Github,期待Star!
https://github.com/ZengLingYong/blog
做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。