---
# System prepended metadata

title: 9. Composite
tags: [DesignPatterns]

---

# 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
![](https://i.imgur.com/yFJjmkE.jpg)

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/)

---

## 新手上路，若有錯誤還請告知，謝謝