嗨大家午安
創建型的五個 Pattern 都學完了,今天不講故事了,純粹帶著阿柴回頭看這五天走過的路,把 Factory Method、Abstract Factory、Builder、Singleton、Prototype 攤開來聊一聊
阿柴接手柴咖啡系統,一路上遇到的問題其實都不一樣:
if-else 越疊越高 → Factory Method
| Pattern | 核心問題 | 柴咖啡情境 | 關鍵手法 |
|---|---|---|---|
| Factory Method | 產品怎麼生,還需要加新產品 | 拿鐵、美式、燕麥拿鐵 | 定義工廠基底類別或介面,由子類別(如 LatteStore)決定並實作 CreateDrink() |
| Abstract Factory | 一組互相搭配、不能混用的產品,要怎麼正確處理 | 早餐套餐、下午茶套餐 | IComboFactory 一次定義多個 CreateXxx(),每個具體工廠只生產同一個家族 |
| Builder | 單一物件的建構參數太多、太複雜,一次到位容易傳錯順序 | 拿鐵客製化選項(size / ice / sugar / milk) | LatteBuilder 用 Fluent 風格一步一步 Set,最後 Build() |
| Singleton | 某個資源全域只該有一份,重複建立會搶資源、卡頓 | 全店只有一台咖啡機的連線 | MachineDriver 用 private 建構子 + static Instance,搭配 Lazy<T> |
| Prototype | 建立新物件成本高,或大部分欄位都跟現成的一樣,想直接複製再改一點點 | 20杯客製化拿鐵共用同一份標準原型 | Clone() 搭配 MemberwiseClone(),注意淺拷貝/深拷貝 |
Factory Method 常常是 Abstract Factory 的建材:
public class BreakfastComboFactory : IComboFactory
{
public IDrink CreateDrink() => new Americano();
public ISideItem CreateSide() => new Bagel();
}
public class AfternoonTeaComboFactory : IComboFactory
{
public IDrink CreateDrink() => new Latte();
public ISideItem CreateSide() => new Cake();
}
Day 10 的 BreakfastComboFactory 內部就是靠兩個工廠方法(CreateDrink()、CreateSide())組成的,Abstract Factory 管的是「這幾個產品要不要綁成一套」,不是取代 Factory Method,而是疊在它上面多管一層「成套關係」
Builder 跟 Factory 系列關心的層次不一樣:Factory Method、Abstract Factory 回答的是「該生出哪一種(或哪一組)產品」
Builder 回答的是「這一個物件,內部細節該怎麼一步步組裝出來」
兩者可以同時出現,比如某個工廠內部要組出一杯複雜的飲料,實作裡一樣可以呼叫 LatteBuilder
public class LatteBuilder
{
private CupSize _size = CupSize.Medium; // 預設中杯
private IceLevel _iceLevel = IceLevel.Normal; // 預設正常冰
private SugarLevel _sugarLevel = SugarLevel.Full; // 預設正常甜
private MilkType _milkType = MilkType.Regular; // 預設鮮奶
public LatteBuilder SetSize(CupSize size) { _size = size; return this; }
public LatteBuilder SetIce(IceLevel iceLevel) { _iceLevel = iceLevel; return this; }
public LatteBuilder SetSugar(SugarLevel sugarLevel) { _sugarLevel = sugarLevel; return this; }
public LatteBuilder SetMilk(MilkType milkType) { _milkType = milkType; return this; }
// 統一在這組裝,也是集中放驗證邏輯的好地方
public Latte Build()
{
return new Latte(_size, _iceLevel, _sugarLevel, _milkType);
}
}
Singleton 管的維度跟前面三個都不同:
Factory Method、Abstract Factory、Builder 談的都是「怎麼生」
Singleton 談的是「生出來的東西,全域該不該只存在一份」。它幾乎可以跟任何一個 Pattern 同時出現,比如 MachineDriver 這個 Singleton,內部要接一台複雜的硬體,一樣可以用 Builder 組裝它的連線設定
public sealed class MachineDriver
{
// 1. 用 Lazy<T> 包一層,確保「第一次真的有人要點餐」時才建立這條連線,且天生執行緒安全
private static readonly Lazy<MachineDriver> _instance = new(() => new MachineDriver());
// 2. 唯一的全域存取點
public static MachineDriver Instance => _instance.Value;
// 3. 建構子上鎖,外部沒辦法自己 new MachineDriver()
private MachineDriver()
{
Console.WriteLine("--> 正在連線咖啡機硬體...");
// 這裡會開 TCP / Serial 連線,跟咖啡機握手
}
public void Brew(string drinkName)
{
Console.WriteLine($"正在萃取:{drinkName}");
}
}
Prototype 站在 Factory/Builder 的對立面:
Factory Method 跟 Builder 都是「照著配方從零組裝一次」
Prototype 是「不想每次都重新照配方走一次,直接拿現成的複製,改一點點就好」
同樣是拿鐵需求,Day 11 用 LatteBuilder 從零組,Day 13 用 Clone() 複製標準原型,兩條路線是互補
建構便宜、選項單純時用 Builder 現組;有現成的標準品、只想微調時用 Prototype 複製
public class ChaiLatte
{
// ...前面屬性保持不變
public List<string> Toppings { get; set; } = new List<string>();
public ChaiLatte Clone()
{
// 1. 先透過 MemberwiseClone 複製基本型別
var clone = (ChaiLatte)this.MemberwiseClone();
// 2. 手動為參考型別(List)建立一份全新實體(深拷貝)
clone.Toppings = new List<string>(this.Toppings);
return clone;
}
}
如果阿柴把「幫我重新設計柴咖啡的點餐系統,物件建立的部分要好維護」整包丟給 AI 處理
很容易看到一種:AI 一口氣把 Factory Method、Abstract Factory、Builder、Singleton、Prototype 全部套上去
飲料要用工廠生、套餐要用抽象工廠包、客製化要用建造者組、菜單設定弄成單例、常客配方再包一層原型
每個 Pattern 單獨看語法都對,湊在一起卻是將複雜度跟認知負擔直線上升
把這五題想清楚、明確告訴 AI「柴咖啡的規模、痛點在這裡」
AI 給出的創建型架構才會符合這間店現在需要的樣子,而不是五個 Pattern 各自處理、湊在一起卻互相打架的,然後 review 的人也不懂 design pattern,不知從何維護
明天開始進入結構型 Pattern,柴咖啡要跟外送平台談合作了我們,先從 Decorator 處理開始