不管是在生活中仍是項目中,咱們常常會遇到具備「部分-總體」概念的對象,好比員工與團隊的關係,這就相似樹形結構,可能具備不少的嵌套層次和分支,把這種複雜性直接暴露給調用端是不合適的。編程
藉助組合模式,能夠將這類具備「部分-總體」的對象組合成樹形的層次結構,並使得用戶能夠對單個對象和組合對象採用相同的使用方式。
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類圖:
安全
組合模式包含三個角色:ide
在使用組合模式時,根據抽象構件類的定義形式,可將組合模式分爲透明模式和安全組合兩種形式。this
透明模式中,抽象構件Component中聲明瞭全部用於管理成員對象的方法,包括add()、remove()以及getChildren()等方法,這樣作的好處是確保全部的構件類都有相同的接口。在客戶端看來,葉子對象與容器對象所提供的方法是一致的,客戶端能夠相同地對待全部的對象。透明組合模式也是組合模式的標準形式,前面的類圖表示的就是透明模式。設計
透明模式的缺點是不夠安全,由於葉子對象和容器對象在本質上是有區別的。葉子對象不可能有下一個層次的對象,即不可能包含成員對象,所以爲其提供add()、remove()以及getChildren()等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段若是調用這些方法就會致使異常。code
透明模式的實現代碼以下:orm
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中沒有聲明任何用於管理成員對象的方法,這樣能夠保證安全,葉子對象中沒法調用到那些管理成員對象的方法。對象
安全模式的缺點是不夠透明,由於葉子構件和容器構件具備不一樣的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,所以客戶端不能徹底針對抽象編程,必須有區別地對待葉子構件和容器構件。blog
將對象組合成樹形結構後,要使用這些對象,就須要用遍歷樹形結構的方式來獲取這些對象。
好比對於上面代碼中的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#的工程化實現及擴展》