關於 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
- 將復雜物件的傳入參數由建構子中抽離,以便相同的建構子可以依據不同的傳入參數而有不同樣貌
Builder(建造者)屬於創建型(Creational Patterns),當遇到初始化步驟複雜的類別時,使用 Builder 來包裝初始化邏輯,呼叫端依據需求選擇相對應的 Builder 實作,以減低使用的複雜度,當遇到需要於傳入參數加上約束條件時,可使用 Builder 來處理相關邏輯。
優點:
- 符合 單一職責原則(Single Responsibility Principle)
- 可簡化傳入參數過多問題,並將複雜邏輯抽離
缺點:
- 會增加許多 class 造成程式複雜度增加
2. UML
Class 間關聯:
- Director 可包含 Builder
- ConcreteBuilder 繼承 Builder
- ConcreteBuilder 依賴 Product
Class:
- Director:定義建造的流程細節
- Builder:建造者的抽象類別或介面
- ConcreteBuilder:建造者的實作,用來取得建構子中的複雜參數 & 構造
Product
- Product:正在構造的複雜物件
3. 將 UML 轉為程式碼
正在構造的複雜物件
/// <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);
}
}
}
建造者方法的介面
/// <summary>
/// 建造者方法的介面
/// </summary>
public interface IBuilder
{
void BuildPartA();
void BuildPartB();
Product GetResult();
}
建造者方法的實作
/// <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;
}
}
定義建造的流程細節
/// <summary>
/// 定義建造的流程細節
/// </summary>
public class Director
{
public void Construct(IBuilder builder)
{
builder.BuildPartA();
builder.BuildPartB();
}
}
- 建立
director
- 建立
builder
並傳入director
中,透過director
定義構造流程 - 透過
builder
建立product
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();
}
執行結果
Product Parts -------
PartA1
PartB1
Product Parts -------
PartA2
PartB2
4. 情境
我們接到了一個付款的需求
- 需要能支援現有的兩種付款方式(現金、ApplePay)
- 要依據付款方式提供折扣
- 為了因應周年慶,還需要依據付款方式加上贈品、紅利點數功能
- 且未來可能會有更多不同的付款方式
處理折扣、贈品、紅利點數等複雜動作
/// <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)} 元");
}
}
建造者方法的介面
/// <summary>
/// 建造者方法的介面
/// </summary>
public interface IBuilder
{
// 折扣
void Discount();
// 紅利點數
void RewardPoints();
// 贈品
void Giveaway();
Product GetPayment();
}
依據現金 & ApplePay 實作折扣、贈品、紅利點數
/// <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;
}
}
定義建造的流程細節
/// <summary>
/// 定義建造的流程細節
/// </summary>
public class Director
{
public void Construct(IBuilder builder)
{
// 加入折扣
builder.Discount();
// 加入紅利點數
builder.RewardPoints();
// 加入贈品
builder.Giveaway();
}
}
- 建立
director
- 建立現金 & ApplePay
builder
並傳入director
中,透過director
定義構造流程 - 透過
builder
建立現金 & ApplePayPayment
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();
}
執行結果
-------
Cash 不打折
Cash 集點
Cash 送馬克杯
應收金額:100 元
-------
ApplePay 九折
ApplePay 集點
ApplePay 無贈品
應收金額:90 元
完整程式碼
GitHub:Creational_03_Builder