關於 Composite 本篇將討論以下幾個問題
1. 關於 Composite
2. UML
3. 將 UML 轉為程式碼
4. 情境
測試環境:
OS:Windows 10
IDE:Visual Studio 2019
1. 關於 Composite
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
by Gang of Four
- 將物件組合成樹結構來表現部分與整體的階層
- 組合可以使呼叫端以同樣方式呼叫單一個體(實體物件)或組合物件(容器)
Composite(組合)屬於結構型(Structural Patterns),當遇到未知階層與數量且可以使用樹結構來呈現的資料時,可以使用 Composite 來保留動態擴展結構的彈性。
優點:
- 符合 開閉原則(Open Closed Principle)
缺點:
- 違反 介面隔離原則(Interface Segregation Principle)
2. UML
Class 間關聯:
- Client 關聯 Component
- Leaf & Composite 繼承 Component
- Composite 可包含 Component
Class:
- Client:呼叫端
- Component:定義呼叫端接口與管理子階層的接口
- Leaf:定義組合中實體物件,且沒有下一階層
- Composite:定義子階層,並存放實體物件或容器
3. 將 UML 轉為程式碼
定義容器 interface
/// <summary>
/// 定義容器 interface
/// </summary>
public interface IComponent
{
string Name { get; }
void Add(IComponent c);
void Remove(IComponent c);
void Display(int depth);
}
容器實作
/// <summary>
/// 容器實作
/// </summary>
public class Composite : IComponent
{
private readonly List<IComponent> _children = new List<IComponent>();
public string Name { get; }
public Composite(string name)
{
Name = name;
}
public void Add(IComponent component)
{
_children.Add(component);
}
public void Remove(IComponent component)
{
_children.Remove(component);
}
public void Display(int depth)
{
var itemName = depth > 0 ? new string('+', depth) + " " + Name : Name;
Console.WriteLine(itemName);
foreach (IComponent component in _children)
{
component.Display(depth + 3);
}
}
}
實體物件實作
/// <summary>
/// 實體物件實作
/// </summary>
public class Leaf : IComponent
{
public string Name { get; }
public Leaf(string name)
{
Name = name;
}
public void Add(IComponent component)
{
Console.WriteLine("Cannot add child to leaf");
}
public void Remove(IComponent component)
{
Console.WriteLine("Cannot remove child from leaf");
}
public void Display(int depth)
{
Console.WriteLine(new string('-', depth) + " " + Name);
}
}
- 建立樹狀結構
root
- 於樹狀結構中新增/移除容器
composite
& 實體物件Leaf
static void Main(string[] args)
{
// 樹結構
// 建立根目錄
Default.Composite root = new Default.Composite("root");
root.Add(new Default.Leaf("項目 A"));
root.Add(new Default.Leaf("項目 B"));
// 建立容器 1
Default.Composite composite1 = new Default.Composite("容器 1");
composite1.Add(new Default.Leaf("項目 C"));
composite1.Add(new Default.Leaf("項目 D"));
// 建立容器 2
Default.Composite composite2 = new Default.Composite("容器 2");
composite2.Add(new Default.Leaf("項目 E"));
// 容器 2 加入 容器 1
composite1.Add(composite2);
// 容器 1 加入根目錄
root.Add(composite1);
root.Add(new Default.Leaf("項目 F"));
Default.Leaf leaf_G = new Default.Leaf("項目 G");
root.Add(leaf_G);
root.Display(0);
Console.WriteLine("\n === 移除 項目 G ===\n");
root.Remove(leaf_G);
root.Display(0);
Console.ReadLine();
}
執行結果
root
--- 項目 A
--- 項目 B
+++ 容器 1
------ 項目 C
------ 項目 D
++++++ 容器 2
--------- 項目 E
--- 項目 F
--- 項目 G
=== 移除 項目 G ===
root
--- 項目 A
--- 項目 B
+++ 容器 1
------ 項目 C
------ 項目 D
++++++ 容器 2
--------- 項目 E
--- 項目 F
4. 情境
我們接到了一個要將眾多門市依地區畫分的需求
- 地區有階層關係(e.g. 台北市>大安區)
- 同一地區若超過 N 間門市時則要再往下拆分一階層 (e.g. 大安區1、大安區2)
定義地區 interface
/// <summary>
/// 定義地區 interface
/// </summary>
public interface IDistrict
{
string Name { get; }
void Add(IDistrict district);
void Remove(IDistrict district);
void Display(int depth);
}
地區實作
/// <summary>
/// 地區實作
/// </summary>
public class District : IDistrict
{
private readonly List<IDistrict> _children = new List<IDistrict>();
public string Name { get; }
public District(string name)
{
Name = name;
}
public void Add(IDistrict district)
{
_children.Add(district);
}
public void Remove(IDistrict district)
{
_children.Remove(district);
}
public void Display(int depth)
{
var itemName = depth > 0 ? new string('+', depth) + " " + Name : Name;
Console.WriteLine(itemName);
foreach (IDistrict district in _children)
{
district.Display(depth + 3);
}
}
}
門市實作
/// <summary>
/// 門市實作
/// </summary>
public class Store : IDistrict
{
public string Name { get; }
public Store(string name)
{
Name = name;
}
public void Add(IDistrict district)
{
Console.WriteLine("門市無法新增子階層");
}
public void Remove(IDistrict district)
{
Console.WriteLine("門市無法移除子階層");
}
public void Display(int depth)
{
Console.WriteLine(new string('-', depth) + " " + Name);
}
}
- 建立地區/門市樹狀結構
rootDistrict
- 於樹狀結構中新增/移除地區
district
& 門市store
static void Main(string[] args)
{
// 樹結構
// 建立根地區
Situation.District rootDistrict = new Situation.District("台北市");
// 建立子地區
Situation.District districtDaan = new Situation.District("大安區");
// 建立大安區1
Situation.District districtDaan1 = new Situation.District("大安區 1");
districtDaan1.Add(new Situation.Store("門市 A"));
districtDaan1.Add(new Situation.Store("門市 B"));
// 建立大安區2
Situation.District districtDaan2 = new Situation.District("大安區 2");
districtDaan2.Add(new Situation.Store("門市 C"));
districtDaan2.Add(new Situation.Store("門市 D"));
// 大安區1 & 大安區2 加入 大安區
districtDaan.Add(districtDaan1);
districtDaan.Add(districtDaan2);
// 大安區 加入 台北市
rootDistrict.Add(districtDaan);
rootDistrict.Display(0);
Console.ReadLine();
}
執行結果
台北市
+++ 大安區
++++++ 大安區 1
--------- 門市 A
--------- 門市 B
++++++ 大安區 2
--------- 門市 C
--------- 門市 D
完整程式碼
GitHub:Structural_03_Composite