【設計模式】組合模式 Composite Pattern

樹形結構是軟件行業很常見的一種結構,幾乎隨處可見,  好比: HTML 頁面中的DOM,產品的分類,一般一些應用或網站的菜單,Windows Form 中的控件繼承關係,Android中的View繼承關係,部門的組織架構,Windows 資源管理器 等等都是樹形結構。編程

image

 

 

 

 

 

 

 

 

 

 

 

 

Windows 資源管理安全

樹形結構是頗有特色的一種數據結構,  下圖是一棵樹:數據結構

image

樹結構有幾個術語:架構

根節點:最高的節點被稱爲根節點,上圖中的紅色節點是根節點。根節點沒有父節點。ide

父節點:若是一個節點的下面連接着其它節點那上層節點被稱做該節點的父節點,下層節點被稱做父節點的子節點。除了根節點外子節點有且只有一個父節點。上圖中的紅色和黃色都是父節點。網站

葉子節點:每個節點有0個或多個子節點。有0個(沒有)子節點的節點稱做葉子節點。上圖中的綠色節點都是葉子節點。this

若是要給樹形結構上增長或者刪除一些節點該如何處理呢? 組合模式提供了面向對象優雅的處理這種數據結構的方法。spa

1、組合模式的定義

組合模式(Composite Pattern):組合多個對象造成樹形結構以表示具備「總體—部分」關係的層次結構。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具備一致性,組合模式又能夠稱爲「總體—部分」(Part-Whole)模式,它是一種對象結構型模式。3d

2、組合模式結構圖

image

組合模式結構圖code

一、Component(抽象構件):

它能夠是接口或抽象類,爲葉子構件和容器構件對象聲明接口,在該角色中能夠包含全部子類共有行爲的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增長子構件、刪除子構件、獲取子構件等。

2 、Composite(容器構件):

它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點能夠是葉子節點,也能夠是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行爲,包括那些訪問及管理子構件的方法,在其業務方法中能夠遞歸調用其子節點的業務方法。

三、Leaf(葉子構件):

它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行爲。對於那些訪問及管理子構件的方法,能夠經過異常等方式進行處理。

3、組合模式的示例代碼

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

  輸出結果

image

4、組合模式實例

如今有一個新聞系統,這個新聞系統裏有若干類別,每個子類裏又包含若干分類,理論上是一個無限級的樹形結構,同時每一個分類上均可能包含具體的新聞。結構以下:

image 新聞系統分類組織機構圖

上圖中的白色部分表示分類(Category),綠色部分表示新聞(News), 如今要添加新聞的分類,刪除新聞,以及將新聞展現出來。下面咱們用組合模式來實現這些功能.

經過分析,能夠提出構件 NewsComponent, News 和Category 三個對象,NewsComponent至關於組合模式的抽象構建,Category至關於 容器構建,News至關於葉子構建。這樣咱們就能夠畫出新聞系統核心UML圖:

image 

新聞系統代碼:

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

輸出結果:

image

5、透明組合模式與安全組合模式

在上面的實例中咱們發現,Component構件類中出現了Add,Remove方法,可是在葉子節點類(Leaf)中不得不去實現,可是葉子節點是不須要這些方法的,看起來有些雞肋。雖然客戶端實現了無差異調用,雖然能夠針對抽象編程,可是一旦調用到了葉子節點的這些方法,軟件可能會出現異常或者無心義的調用。那麼咱們有什麼方法來改變呢?

能夠將這些方法從Component中移到Composite 中,這樣就能夠避免這種狀況。只在Component中定義Composite和Leaf公用的方法。

那麼組合模式的結構圖就變成這樣:
image

這時圖中的各個角色沒有變化,僅僅是在Component去掉了一些抽象方法,在調用的時就不能用抽象構建Component來聲明瞭,要用具體的Composite來聲明,這樣在客戶端調用時就須要區別對待Composite和Leaf了,不能針對抽象Component構件編程了。

一、Component2(抽象構件):

它能夠是接口或抽象類,爲葉子構件和容器構件對象聲明接口,在該角色中能夠包含全部子類共有行爲的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增長子構件、刪除子構件、獲取子構件等。

2 、Composite2(容器構件):

它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點能夠是葉子節點,也能夠是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行爲,包括那些訪問及管理子構件的方法,在其業務方法中能夠遞歸調用其子節點的業務方法。

三、Leaf2(葉子構件):

它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行爲。對於那些訪問及管理子構件的方法,能夠經過異常等方式進行處理。

在組合模式中根據抽象構建定義的形式, 能夠將組合模式更稱 安全組合模式和透明組合模式。

安全組合模式:

安全組合模式只在抽象構件類中定義葉子節點(Leaf)和容器節點(Composite)都共有的方法,將容器方法移至容器節點中, 它的通常結構以下圖:

image 安全組合模式UML圖

安全模式的好處是,在客戶端調用葉子節點時不會出現調用不安全的方法,由於葉子節點沒有該方法,抽象構件中也沒有該方法。很差的地方是,葉子節點和容器節點的實現出現了差異,不能在客戶端統一使用抽象構件(Component)來編程了,必需要區別對待葉子節點(Leaf)和容器節點(Component)。

透明組合模式

透明組合模式是將對容器構件的管理方法都定義在抽象構件(Component)中,在葉子節點(Leaf)和容器節點(Composite)都進行實現,在葉子節點中針對相關的管理方法進行相關異常處理,或者友好提示的處理實現,  通常狀況下透明組合模式的UML以下:

 image

透明模式UML圖

透明模式的好處是能夠給客戶端調用時提供統一,無差異的對待葉子節點(Leaf)和容器節點(Composite),而且能夠針對抽象構件(Component)進行編程。透明模式的缺點是若是調用到葉子節點(Leaf) 上的相關方法會致使程序異常。

6、組合模式的優勢

  1. 組合模式能夠清楚地定義分層次的複雜對象,表示對象的所有或部分層次,它讓客戶端忽略了層次的差別,方便對整個層次結構進行控制。
  2. 客戶端能夠一致地使用一個組合結構或其中單個對象,沒必要關心處理的是單個對象仍是整個組合結構,簡化了客戶端代碼。
  3. 在組合模式中增長新的容器構件和葉子構件都很方便,無須對現有類庫進行任何修改,符合「開閉原則」。
  4. 組合模式爲樹形結構的面向對象實現提供了一種靈活的解決方案,經過葉子對象和容器對象的遞歸組合,能夠造成複雜的樹形結構,但對樹形結構的控制卻很是簡單。

7、組合模式的缺點

組合模式,控制容器節點的類型不太容易。

8、組合模式的使用場景

  1. 在具備總體和部分的層次結構中,但願經過一種方式忽略總體與部分的差別,客戶端能夠一致地對待它們。
  2. 在一個使用面嚮對象語言開發的系統中須要處理一個樹形結構。
  3. 在一個系統中可以分離出葉子對象和容器對象,並且它們的類型不固定,須要增長一些新的類型。

Composite模式在日常的開發過程當中使用的很是多,由於他提供了一種面向對象的操做樹形結構的方法,樹形結構在開發中頻繁出現。

相關文章
相關標籤/搜索