# 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/) --- ## 新手上路,若有錯誤還請告知,謝謝