設計模式(8) 組合模式

  • 組合模式
  • 透明模式與安全模式
  • 對組合的篩選遍歷

無論是在生活中還是項目中,我們經常會遇到具有“部分-整體”概念的對象,比如員工與團隊的關係,這就類似樹形結構,可能具有很多的嵌套層次和分支,把這種複雜性直接暴露給調用端是不合適的。

組合模式

藉助組合模式,可以將這類具有“部分-整體”的對象組合成樹形的層次結構,並使得用戶可以對單個對象和組合對象採用相同的使用方式。
GOF對組合模式的描述爲:
Compose objects into tree structures to represent part-whole hierarchies.
Compositelets clients treat individual objects and compositions of objects uniformly.
— Design Patterns : Elements of Reusable Object-Oriented Software

UML類圖:

組合模式包含三個角色:

  • Leaf:葉子節點,代表單個個體,它沒有子節點。
  • Composite:組合節點,既可以包含葉子節點,也可以包含其他的組合節點,
  • Component:抽象構件,定義Leaf和Composite共有的方法和屬性,可以定義一些默認的行爲或屬性。

透明模式與安全模式

在使用組合模式時,根據抽象構件類的定義形式,可將組合模式分爲透明模式和安全組合兩種形式。

透明模式

透明模式中,抽象構件Component中聲明瞭所有用於管理成員對象的方法,包括add()、remove()以及getChildren()等方法,這樣做的好處是確保所有的構件類都有相同的接口。在客戶端看來,葉子對象與容器對象所提供的方法是一致的,客戶端可以相同地對待所有的對象。透明組合模式也是組合模式的標準形式,前面的類圖表示的就是透明模式。

透明模式的缺點是不夠安全,因爲葉子對象和容器對象在本質上是有區別的。葉子對象不可能有下一個層次的對象,即不可能包含成員對象,因此爲其提供add()、remove()以及getChildren()等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法就會導致異常。

透明模式的實現代碼如下:

public abstract class Component
{
    protected IList<Component> children;

    public virtual string Name { get; set; }

    public virtual void Add(Component child)
    {
        children.Add(child);
    }

    public virtual void Remove(Component child)
    {
        children.Remove(child);
    }

    public virtual Component this[int index]
    {
        get { return children[index]; }
    }
}

public class Leaf : Component
{
    public override void Add(Component child)
    {
        throw new NotSupportedException();
    }
    public override void Remove(Component child)
    {
        throw new NotSupportedException();
    }
    public override Component this[int index] => throw new NotSupportedException();
}

public class Composite : Component
{
    public Composite()
    {
        base.children = new List<Component>();
    }
}

安全模式

安全模式則是將管理成員對象的方法從抽象構件Component轉移到了Composite,在抽象構件Component中沒有聲明任何用於管理成員對象的方法,這樣可以保證安全,葉子對象中無法調用到那些管理成員對象的方法。

安全模式的缺點是不夠透明,因爲葉子構件和容器構件具有不同的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區別地對待葉子構件和容器構件。

對組合的篩選遍歷

將對象組合成樹形結構後,要使用這些對象,就需要用遍歷樹形結構的方式來獲取這些對象。
比如對於上面代碼中的Component,如果需要獲取全部結點的Names屬性

實現代碼可以爲:

public List<string> names = new List<string>();
public virtual IEnumerable<string> GetNameList()
{
    GetNameList(names);
    return names;
}


private virtual void GetNameList(List<string> names)
{
    names.Add(this.Name);
    if (children != null && children.Count > 0)
    {
        foreach (Component child in children)
        {
            child.GetNameList(names);
        }
    }
}

但有的時候往往會遇到一些定製化的遍歷需求,比如只獲取Leaf結點(僅列出一個所有員工的名單),只獲取Composite結點(僅列出所有部門領導的信息)等等,對於這些需求如果一一實現比較麻煩,且需要頻繁變化,可以採用一種更通用的方式,類似Linq中Where篩選那樣,調用的同時把篩選條件也傳入。

對GetNameList方法的擴展:

public virtual IEnumerable<string> GetNameList(Func<Component, bool> isMatchFunc)
{
    GetNameList(names, isMatchFunc);
    return names;
}

public virtual void GetNameList(List<string> names, Func<Component, bool> isMatchFunc)
{
    if (isMatchFunc == null || isMatchFunc(this))
    {
        names.Add(this.Name);
    }
    if (children != null && children.Count > 0)
    {
        foreach (Component child in children)
        {
            child.GetNameList(names, isMatchFunc);
        }
    }
}

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章