# 9. Composite ###### tags: `DesignPatterns` ## 關於 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](https://en.wikipedia.org/wiki/Design_Patterns) - 將物件組合成樹結構來表現部分與整體的階層 - 組合可以使呼叫端以同樣方式呼叫單一個體(實體物件)或組合物件(容器) 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 ```C# /// <summary> /// 定義容器 interface /// </summary> public interface IComponent { string Name { get; } void Add(IComponent c); void Remove(IComponent c); void Display(int depth); } ``` 容器實作 ```C# /// <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); } } } ``` 實體物件實作 ```C# /// <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); } } ``` 1. 建立樹狀結構`root` 2. 於樹狀結構中新增/移除容器`composite` & 實體物件`Leaf` ```C# 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(); } ``` 執行結果 ```Console 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 ```C# /// <summary> /// 定義地區 interface /// </summary> public interface IDistrict { string Name { get; } void Add(IDistrict district); void Remove(IDistrict district); void Display(int depth); } ``` 地區實作 ```C# /// <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); } } } ``` 門市實作 ```C# /// <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); } } ``` 1. 建立地區/門市樹狀結構`rootDistrict` 2. 於樹狀結構中新增/移除地區`district` & 門市`store` ```C# 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(); } ``` 執行結果 ```Console 台北市 +++ 大安區 ++++++ 大安區 1 --------- 門市 A --------- 門市 B ++++++ 大安區 2 --------- 門市 C --------- 門市 D ``` --- ## 完整程式碼 GitHub:[Structural_03_Composite](https://github.com/darionnnnnn/blog/tree/master/Blog/Structural_03_Composite) --- ## 總結 ### 範例中主要展示了樹結構部分,實作中可以將所需邏輯加入到容器(Composite) & 實體物件(Leaf),像是門市每日營業額可以計算之後在容器節點作統計,這樣我們就可以知道每個地區當日營業額是多少。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up