樹形結構是軟體行業很常見的一種結構,幾乎隨處可見, 比如: HTML 頁面中的DOM,產品的分類,通常一些應用或網站的菜單,Windows Form 中的控制項繼承關係,Android中的View繼承關係,部門的組織架構,Windows 資源管理器 等等都是樹形結構。 Windows 資源管理 樹形結 ...
樹形結構是軟體行業很常見的一種結構,幾乎隨處可見, 比如: HTML 頁面中的DOM,產品的分類,通常一些應用或網站的菜單,Windows Form 中的控制項繼承關係,Android中的View繼承關係,部門的組織架構,Windows 資源管理器 等等都是樹形結構。
Windows 資源管理
樹形結構是很有特點的一種數據結構, 下圖是一棵樹:
樹結構有幾個術語:
根節點:最高的節點被稱為根節點,上圖中的紅色節點是根節點。根節點沒有父節點。
父節點:如果一個節點的下麵鏈接著其它節點那上層節點被稱作該節點的父節點,下層節點被稱作父節點的子節點。除了根節點外子節點有且只有一個父節點。上圖中的紅色和黃色都是父節點。
葉子節點:每一個節點有0個或多個子節點。有0個(沒有)子節點的節點稱作葉子節點。上圖中的綠色節點都是葉子節點。
如果要給樹形結構上增加或者刪除一些節點該如何處理呢? 組合模式提供了面向對象優雅的處理這種數據結構的方法。
一、組合模式的定義
組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關係的層次結構。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。
二、組合模式結構圖
組合模式結構圖
1、Component(抽象構件):
它可以是介面或抽象類,為葉子構件和容器構件對象聲明介面,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。
2 、Composite(容器構件):
它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是葉子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。
3、Leaf(葉子構件):
它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。
三、組合模式的示例代碼
public abstract class Component { public abstract void Operaton(int depth); public abstract void Add(Component component); public abstract void Remove(Component component); public abstract Component GetChild(int i); } public class Composite : Component { private IList<Component> components = new List<Component>(); public override void Operaton(int depth) { Console.WriteLine(new String(' ', depth - 2) + "|"); Console.WriteLine(new String(' ', depth - 2) + "--" + this.GetType().Name + " Opration"); foreach (var component in components) { component.Operaton(depth + 2); } } public override void Add(Component component) { components.Add(component); } public override void Remove(Component component) { components.Remove(component); } public override Component GetChild(int i) { return components[i]; } } public class Leaf : Component { public override void Operaton(int depth) { Console.WriteLine(new String(' ', depth - 2) + "|"); Console.WriteLine(new String(' ', depth - 2) + "--" + this.GetType().Name + " Opration"); } public override void Add(Component component) { Console.WriteLine("Cannot add to a leaf"); } public override void Remove(Component component) { Console.WriteLine("Cannot remove from a leaf"); } public override Component GetChild(int i) { throw new Exception("Cannot get a child from a leaf"); } }
客戶端調用:
static void Main(string[] args) { Component root = new Composite.Structure.Composite(); root.Add(new Leaf()); root.Add(new Leaf()); var composite = new Composite.Structure.Composite(); composite.Add(new Leaf()); composite.Add(new Leaf()); root.Add(composite); var componsite2 = new Composite.Structure.Composite(); composite.Add(componsite2); componsite2.Add(new Leaf()); componsite2.Add(new Leaf()); root.Operaton(2); Console.ReadKey(); }
輸出結果
四、組合模式實例
現在有一個新聞系統,這個新聞系統里有若幹類別,每一個子類里又包含若幹分類,理論上是一個無限級的樹形結構,同時每個分類上都可能包含具體的新聞。結構如下:
上圖中的白色部分表示分類(Category),綠色部分表示新聞(News), 現在要添加新聞的分類,刪除新聞,以及將新聞展示出來。下麵我們用組合模式來實現這些功能.
通過分析,可以提出構件 NewsComponent, News 和Category 三個對象,NewsComponent相當於組合模式的抽象構建,Category相當於 容器構建,News相當於葉子構建。這樣我們就可以畫出新聞系統核心UML圖:
新聞系統代碼:
public abstract class NewsComponent { protected string title; public NewsComponent(string title) { this.title = title; } public abstract void Add(NewsComponent newsComponent); public abstract void Remove(NewsComponent newsComponent); public abstract void Display(int depath); } public class Category : NewsComponent { private IList<NewsComponent> categories = new List<NewsComponent>(); public Category(string title):base(title) { } public override void Add(NewsComponent newsComponent) { categories.Add(newsComponent); } public override void Remove(NewsComponent newsComponent) { categories.Add(newsComponent); } public override void Display(int depath) { Console.WriteLine(new String('-', depath) + title); foreach (var category in categories) { category.Display(depath+2); } } } public class News: NewsComponent { private string content; public News(string title, string content):base(title) { this.content = content; } public override void Add(NewsComponent newsComponent) { Console.WriteLine("Cannot add to a News"); } public override void Remove(NewsComponent newsComponent) { Console.WriteLine("Cannot remove from a News"); } public override void Display(int depath) { Console.WriteLine(new String('-', depath) + this.title + "[content]:" + this.content); } }
客戶端調用:
static void Main(string[] args) { NewsComponent newsComponent = new Category("新聞"); newsComponent.Add(new Category("政治新聞")); newsComponent.Add(new News("【政治頭條】-美國大選 特朗普生出將於將在北京時間明日凌晨3:45宣誓就職美國總統", "...")); newsComponent.Add(new Category("政治風雲")); var a = new Category("娛樂新聞"); a.Add(new News("【娛樂頭條】-由王安全執導的<<監獄風雲>>將於今天0點在各大影院全面上映", "....")); a.Add(new Category("娛樂八卦")); newsComponent.Add(a); var b = new Category("財經新聞"); b.Add(new News("【財經頭條】-由於受土耳其貨幣危機的影響,美國及歐洲股市全線跌幅超1%", "...")); var c = new Category("股市風雲"); b.Add(c); c.Add(new Category("歐洲股市")); c.Add(new Category("美股風雲")); c.Add(new Category("日韓股市")); newsComponent.Add(b); var d = new Category("體育新聞"); var e = new Category("籃球"); d.Add(e); var f=new Category("NBA"); var g= new Category("CBA"); e.Add(g); e.Add(f); newsComponent.Add(d); newsComponent.Display(1); Console.ReadKey(); }
輸出結果:
五、透明組合模式與安全組合模式
在上面的實例中我們發現,Component構件類中出現了Add,Remove方法,但是在葉子節點類(Leaf)中不得不去實現,但是葉子節點是不需要這些方法的,看起來有些雞肋。雖然客戶端實現了無差別調用,雖然可以針對抽象編程,但是一旦調用到了葉子節點的這些方法,軟體可能會出現異常或者無意義的調用。那麼我們有什麼方法來改變呢?
可以將這些方法從Component中移到Composite 中,這樣就可以避免這種情況。只在Component中定義Composite和Leaf公用的方法。
這時圖中的各個角色沒有變化,僅僅是在Component去掉了一些抽象方法,在調用的時就不能用抽象構建Component來聲明瞭,要用具體的Composite來聲明,這樣在客戶端調用時就需要區別對待Composite和Leaf了,不能針對抽象Component構件編程了。
1、Component2(抽象構件):
它可以是介面或抽象類,為葉子構件和容器構件對象聲明介面,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。
2 、Composite2(容器構件):
它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是葉子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。
3、Leaf2(葉子構件):
它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。
在組合模式中根據抽象構建定義的形式, 可以將組合模式更稱 安全組合模式和透明組合模式。
安全組合模式:
安全組合模式只在抽象構件類中定義葉子節點(Leaf)和容器節點(Composite)都共有的方法,將容器方法移至容器節點中, 它的一般結構如下圖:
安全模式的好處是,在客戶端調用葉子節點時不會出現調用不安全的方法,因為葉子節點沒有該方法,抽象構件中也沒有該方法。不好的地方是,葉子節點和容器節點的實現出現了差別,不能在客戶端統一使用抽象構件(Component)來編程了,必須要區別對待葉子節點(Leaf)和容器節點(Component)。
透明組合模式
透明組合模式是將對容器構件的管理方法都定義在抽象構件(Component)中,在葉子節點(Leaf)和容器節點(Composite)都進行實現,在葉子節點中針對相關的管理方法進行相關異常處理,或者友好提示的處理實現, 一般情況下透明組合模式的UML如下:
透明模式UML圖
透明模式的好處是可以給客戶端調用時提供統一,無差別的對待葉子節點(Leaf)和容器節點(Composite),並且可以針對抽象構件(Component)進行編程。透明模式的缺點是如果調用到葉子節點(Leaf) 上的相關方法會導致程式異常。
六、組合模式的優點
- 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。
- 客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。
- 在組合模式中增加新的容器構件和葉子構件都很方便,無須對現有類庫進行任何修改,符合“開閉原則”。
- 組合模式為樹形結構的面向對象實現提供了一種靈活的解決方案,通過葉子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。
七、組合模式的缺點
組合模式,控制容器節點的類型不太容易。
八、組合模式的使用場景
- 在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對待它們。
- 在一個使用面向對象語言開發的系統中需要處理一個樹形結構。
- 在一個系統中能夠分離出葉子對象和容器對象,而且它們的類型不固定,需要增加一些新的類型。
Composite模式在平常的開發過程中使用的非常多,因為他提供了一種面向對象的操作樹形結構的方法,樹形結構在開發中頻繁出現。