---
# System prepended metadata

title: 4. Builder
tags: [DesignPatterns]

---

# 4. Builder

###### tags: `DesignPatterns`

## 關於 Builder 本篇將討論以下幾個問題

> ### 1. 關於 Builder
> ### 2. UML
> ### 3. 將 UML 轉為程式碼
> ### 4. 情境

---
## 測試環境：
>OS：Windows 10
>IDE：Visual Studio 2019
   
---
## 1. 關於 Builder
> Separate the construction of a complex object from its representation so that the same construction process can create different representations.
> 
> by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns)

- 將復雜物件的傳入參數由建構子中抽離，以便相同的建構子可以依據不同的傳入參數而有不同樣貌

Builder(建造者)屬於創建型(Creational Patterns)，當遇到**初始化步驟複雜的類別**時，使用 Builder 來包裝初始化邏輯，呼叫端依據需求選擇相對應的 Builder 實作，以減低使用的複雜度，當遇到需要**於傳入參數加上約束條件**時，可使用 Builder 來處理相關邏輯。

優點：
- 符合 單一職責原則(Single Responsibility Principle)
- 可簡化傳入參數過多問題，並將複雜邏輯抽離

缺點：
- 會增加許多 class 造成程式複雜度增加

---
## 2. UML
![](https://i.imgur.com/CoabXZJ.jpg)

Class 間關聯：
- Director 可包含 Builder
- ConcreteBuilder 繼承 Builder
- ConcreteBuilder 依賴 Product

Class：
- Director：定義建造的流程細節
- Builder：建造者的抽象類別或介面
- ConcreteBuilder：建造者的實作，用來取得建構子中的複雜參數 & 構造`Product`
- Product：正在構造的複雜物件

---
## 3. 將 UML 轉為程式碼
正在構造的複雜物件
```C#
/// <summary>
/// 正在構造的複雜物件
/// </summary>
public class Product
{
    private List<string> _parts = new List<string>();

    public void Add(string part)
    {
        _parts.Add(part);
    }

    public void Show()
    {
        Console.WriteLine("\nProduct Parts -------");

        foreach (string part in _parts)
        {
            Console.WriteLine(part);
        }
    }
}
```
建造者方法的介面
```C#
/// <summary>
/// 建造者方法的介面
/// </summary>
public interface IBuilder
{
    void BuildPartA();
    void BuildPartB();
    Product GetResult();
}
```
建造者方法的實作
```C#
/// <summary>
/// 建造者方法的實作 1
/// </summary>
public class ConcreteBuilder1 : IBuilder
{
    private Product _product = new Product();

    public void BuildPartA()
    {
        _product.Add("PartA1");
    }

    public void BuildPartB()
    {
        _product.Add("PartB1");
    }

    public Product GetResult()
    {
        return _product;
    }
}

/// <summary>
/// 建造者方法的實作 2
/// </summary>
public class ConcreteBuilder2 : IBuilder
{
    private Product _product = new Product();

    public void BuildPartA()
    {
        _product.Add("PartA2");
    }

    public void BuildPartB()
    {
        _product.Add("PartB2");
    }

    public Product GetResult()
    {
        return _product;
    }
}
```
定義建造的流程細節
```C#
/// <summary>
/// 定義建造的流程細節
/// </summary>
public class Director
{
    public void Construct(IBuilder builder)
    {
        builder.BuildPartA();
        builder.BuildPartB();
    }
}
```

1. 建立`director`
2. 建立`builder`並傳入`director`中，透過`director`定義構造流程
3. 透過`builder`建立`product`

```C#
static void Main(string[] args)
{
    Default.Director director = new Default.Director();
    
    var builder1 = new Default.ConcreteBuilder1();
    var builder2 = new Default.ConcreteBuilder2();
    
    director.Construct(builder1);
    var product1 = builder1.GetResult();
    product1.Show();
    
    director.Construct(builder2);
    var product2 = builder2.GetResult();
    product2.Show();

    Console.ReadLine();
}
```
執行結果
```Console
Product Parts -------
PartA1
PartB1

Product Parts -------
PartA2
PartB2
```

---
## 4. 情境
我們接到了一個付款的需求
- 需要能支援現有的兩種付款方式(現金、ApplePay)
- 要依據付款方式提供折扣
- 為了因應周年慶，還需要依據付款方式加上贈品、紅利點數功能
- 且未來可能會有更多不同的付款方式

處理折扣、贈品、紅利點數等複雜動作
```C#
/// <summary>
/// 正在構造的複雜物件
/// </summary>
public class Product
{
    private List<string> _parts = new List<string>();

    private double _discount { get; set; }

    public void SetDiscount(double discount)
    {
        _discount = discount;
    }

    public void Add(string part)
    {
        _parts.Add(part);
    }

    public void Pay(int amount)
    {
        Console.WriteLine("\n-------");

        foreach (string part in _parts)
        {
            Console.WriteLine(part);
        }

        Console.WriteLine($"應收金額：{(int)(amount * _discount)} 元");
    }
}
```
建造者方法的介面
```C#
/// <summary>
/// 建造者方法的介面
/// </summary>
public interface IBuilder
{
    // 折扣
    void Discount();
    // 紅利點數
    void RewardPoints();
    // 贈品
    void Giveaway();

    Product GetPayment();
}
```
依據現金 & ApplePay 實作折扣、贈品、紅利點數
```C#
/// <summary>
/// 建造者方法的現金實作
/// </summary>
public class CashBuilder : IBuilder
{
    private Product _product = new Product();

    public void Discount()
    {
        _product.Add("Cash 不打折");
        _product.SetDiscount(1);
    }

    public void RewardPoints()
    {
        _product.Add("Cash 集點");
    }

    public void Giveaway()
    {
        _product.Add("Cash 送馬克杯");
    }

    public Product GetPayment()
    {
        return _product;
    }
}

/// <summary>
/// 建造者方法的 ApplePay 實作
/// </summary>
public class ApplePayBuilder : IBuilder
{
    private Product _product = new Product();

    public void Discount()
    {
        _product.Add("ApplePay 九折");
        _product.SetDiscount(0.9);
    }

    public void RewardPoints()
    {
        _product.Add("ApplePay 集點");
    }

    public void Giveaway()
    {
        _product.Add("ApplePay 無贈品");
    }

    public Product GetPayment()
    {
        return _product;
    }
}
```
定義建造的流程細節
```C#
/// <summary>
/// 定義建造的流程細節
/// </summary>
public class Director
{
    public void Construct(IBuilder builder)
    {
        // 加入折扣
        builder.Discount();
        // 加入紅利點數
        builder.RewardPoints();
        // 加入贈品
        builder.Giveaway();
    }
}
```

1. 建立`director`
2. 建立現金 & ApplePay `builder`並傳入`director`中，透過`director`定義構造流程
3. 透過`builder`建立現金 & ApplePay `Payment`

```C#
static void Main(string[] args)
{
    Situation.Director director = new Situation.Director();
    
    var cashBuilder = new Situation.CashBuilder();
    var applePayBuilder = new Situation.ApplePayBuilder();
    
    director.Construct(cashBuilder);
    var cash = cashBuilder.GetPayment();
    cash.Pay(100);
    
    director.Construct(applePayBuilder);
    var applePay = applePayBuilder.GetPayment();
    applePay.Pay(100);

    Console.ReadLine();
}
```
執行結果
```Console
-------
Cash 不打折
Cash 集點
Cash 送馬克杯
應收金額：100 元

-------
ApplePay 九折
ApplePay 集點
ApplePay 無贈品
應收金額：90 元
```

---
## 完整程式碼
GitHub：[Creational_03_Builder](https://github.com/darionnnnnn/blog/tree/master/Blog/Creational_03_Builder)
   
---
## 總結
### 由於語言特性的關係，C# 可以使用多載的方式減少 Constructor 參數，在情境較為複雜的情況下可以使用 Builder，而較為單純的情況時則可使用多載的方式處理。

---
## 參考資料
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/)

---

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