定義:(Composite Pattern)
將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。
又稱為部分整體模式,合成模式。
類圖:
從圖中可以看到,主要有以下幾個角色:
- Component(根節點): 定義組合對象公用的方法或屬性
- Composite(樹枝節點):組合樹枝節點和樹葉節點構造一個樹形結構
- Leaf(葉子節點):最小單元
啟示:
提到組合模式,大家可能會覺得陌生,但是提到樹形結構,大家可能就很熟悉,畢竟生活中到處都是“樹”。那在Coding中,最常見的樹形結構比如組織架構,菜單,權限設計,文件管理等等。那這次我們就用組織架構來談談組合模式的應用。一個公司是由部門組成的,部門下是員工,典型的組合模式。
就一般公司的組織架構而言,最上層為CEO,也就是根節點,往下就是各部門經理,每個部門經理可能負責有多個項目,每個項目都安排有對應的項目組長負責,那項目組就是由項目成員組成了。為了演示方便,我們的層級就不往下深入了。下面是我簡化的組織架構圖。
代碼:
在我們的組織架構中我們主要分為三塊,組織、部門、員工。
首先來看看Component角色,對應代碼中的Organization
。其中主要定義了公用的屬性,職位、姓名及上級節點。上級節點的主要作用是用來從下往上遍歷。
/// <summary>
/// 組織架構
/// </summary>
public abstract class Organization
{
/// <summary>
/// 成員姓名
/// </summary>
public string MemberName { get; set; }
/// <summary>
/// 成員職位
/// </summary>
public string MemberPosition { get; set; }
/// <summary>
/// 直接上級
/// </summary>
public Organization ParentNode { get; set; }
public void Display()
{
var basicInfo = string.Format("姓名:{0},職位:{1}", MemberName, MemberPosition);
var parentInfo = ParentNode == null
? ""
: string.Format(",直接上級:『姓名:{0},職位:{1}』", ParentNode.MemberName, ParentNode.MemberPosition);
Console.WriteLine(basicInfo + parentInfo);
}
}
再來看看對應Composite角色,部門的實現。在組織架構中,部門是組合的關鍵,也就是樹枝節點。
/// <summary>
/// 部門
/// </summary>
public class Department : Organization
{
private readonly List<Organization> _organizationInfo = new List<Organization>();
public Department(string departmentName, string charge)
{
MemberPosition = departmentName;
MemberName = charge;
}
public void Add(Organization org)
{
_organizationInfo.Add(org);
org.ParentNode = this;
}
public void Remove(Organization org)
{
_organizationInfo.Remove(org);
}
public List<Organization> GetDepartmentMembers()
{
return _organizationInfo;
}
}
最后來看樹葉節點,員工的實現。因為我們在組織節點未定義抽象方法,所以,直接繼承自Organization
即可。
/// <summary>
/// 員工
/// </summary>
public class Member : Organization
{
}
來看看場景類:
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("組合模式:");
Console.WriteLine("-------------------");
var organzation = new Department("CEO", "老總");
var developDepart = new Department("研發部經理", "研哥");
organzation.Add(developDepart);
var projectA = new Department("Erp項目組長", "E哥");
developDepart.Add(projectA);
var memberX = new Member {MemberPosition = "開發工程師", MemberName = "開發X"};
var memberY = new Member {MemberPosition = "開發工程師", MemberName = "開發Y"};
var memberZ = new Member {MemberPosition = "測試工程師", MemberName = "測試Z"};
projectA.Add(memberX);
projectA.Add(memberY);
projectA.Add(memberZ);
Console.WriteLine("組合模式:從上往下遍歷");
DisplayStructure(organzation);
Console.WriteLine("-------------------");
Console.WriteLine();
Console.WriteLine("組合模式:從下往上遍歷");
FindParent(memberX);
Console.WriteLine("-------------------");
Console.ReadLine();
}
/// <summary>
/// 正序排序
/// </summary>
/// <param name="org"></param>
private static void DisplayStructure(Organization org)
{
if (org.ParentNode == null)
org.Display();
var departMent = (Department) org;
foreach (var depart in departMent.GetDepartmentMembers())
{
depart.Display();
if (!(depart is Member))
DisplayStructure((Department) depart);
}
}
/// <summary>
/// 倒序排序
/// </summary>
/// <param name="member"></param>
private static void FindParent(Organization member)
{
member.Display();
while (member.ParentNode != null)
{
member.ParentNode.Display();
member = member.ParentNode;
}
}
}
在場景類中,我們創建了組織架構圖中左邊的這條線,并提供了從上往下、從下往上兩種遍歷方法。輸出結果如下:
本實例主要用于簡單演示組合模式的用法,還有很多需要完善的地方。小伙伴不妨想一想如果在我們真實的項目中該如何設計呢?
擴展
在組合模式中有一種透明組合模式,之所以透明,是因為將composite角色中也就是樹葉節點的用來組合的方法挪到了component角色根節點抽象類中定義,再由樹枝樹葉單獨實現。這樣是將樹枝和樹葉的結構保存了一致,但引入了一個問題,因為樹葉節點最小單元是不需要去組裝的,但是又必須實現抽象類定義的組合方法,就只能給一個空實現throw new NotImplementedException()
,處理不當就會拋出異常。
那相對來說,我們上例就是組合模式的安全實現。
總結:
組合模式的目的是簡化部分整體的組裝方式及遍歷方式。高層模塊(客戶端)通過統一處理Composite角色,就可以完成部分整體的組裝和拆解。根據自己的需求,在Component中定義公用的方法由樹枝樹葉實現,比如:通過定義設置獲取上級節點的方法,即可完成從下往上遍歷。
優缺點:
優點
- 擴展性好:樹枝和樹葉節點可以自由擴展,符合OCP。
- 方便高層調用:局部和整體對高層模塊來說沒有區別,樹形機構中的所有節點都是Component。
缺點
- 不是面向接口編程,與依賴倒置原則相違背。
應用場景:
- 樹形結構
- 部分整體關系