關於 Factory Method 本篇將討論以下幾個問題
1. 關於 Factory Method
2. UML
3. 將 UML 轉為程式碼
4. 情境
5. Simple Factory
測試環境:
OS:Windows 10
IDE:Visual Studio 2019
1. 關於 Factory Method
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
by Gang of Four
- 定義用來創建物件的介面,但實例化哪個類別由子類別決定
工廠方法將實例化延遲至子類別
Factory Method(工廠方法)屬於創建型(Creational Patterns),當遇到需要依靠多個 if/else 來判斷創建哪個實體時,使用 Factory Method 來將依據需求取得相對應實體的這個部分抽象化,由於外部不需要了解取得實體的細節,使得相依減低,進而達到高內聚低耦合的目的。
優點:
- 符合 單一職責原則(Single Responsibility Principle)
- 符合 開閉原則(Open Closed Principle) (Simple Factory 不符合)
缺點:
- 會增加許多 class 造成程式複雜度增加
2. UML
Class 間關聯:
- ConcreteProduct 繼承 Product
- ConcreteCreator 繼承 Creator
- ConcreteCreator 依賴 ConcreteProduct
Class:
- Product:處理邏輯的抽象類別或介面
- ConcreteProduct:處理邏輯的實作
- Creator:工廠方法的抽象類別或介面
- ConcreteCreator:工廠方法的實作,用來創建處理邏輯的實體
3. 將 UML 轉為程式碼
開始之前先說明一下,UML 上雖然是以子類別繼承父類別來標示關聯,但範例中沒有多層繼承關係,且平常 interface 使用 DI 較為方便,C# 8.0 已支援預設實作(MSDN),所以在範例程式碼的撰寫上都會盡量以 interface 取代 abstract class。
ConcreteProduct A / B 皆實作 IProduct 介面
/// <summary>
/// 處理邏輯的介面
/// </summary>
public interface IProduct
{
}
/// <summary>
/// 處理邏輯的實作 A
/// </summary>
public class ConcreteProductA : IProduct
{
}
/// <summary>
/// 處理邏輯的實作 B
/// </summary>
public class ConcreteProductB : IProduct
{
}
工廠方法的介面
/// <summary>
/// 工廠方法的介面
/// </summary>
public interface ICreator
{
public IProduct FactoryMethod();
}
ConcreteCreator A / B 皆實作 ICreator 介面
/// <summary>
/// 工廠方法的實作 A
/// </summary>
public class ConcreteCreatorA : ICreator
{
public IProduct FactoryMethod()
{
return new ConcreteProductA();
}
}
/// <summary>
/// 工廠方法的實作 B
/// </summary>
public class ConcreteCreatorB : ICreator
{
public IProduct FactoryMethod()
{
return new ConcreteProductB();
}
}
- 透過
creator.FactoryMethod()
取得product
實體
static void Main(string[] args)
{
var creators = new List<ICreator>
{
new ConcreteCreatorA(),
new ConcreteCreatorB()
};
foreach (var creator in creators)
{
IProduct product = creator.FactoryMethod();
Console.WriteLine($"Created {product.GetType().Name}");
}
Console.ReadLine();
}
執行結果
Created ConcreteProductA
Created ConcreteProductB
4. 情境
我們接到了一個付款的需求
- 需要能支援現有的兩種付款方式(現金、ApplePay)
- 且未來可能會有更多不同的付款方式
付款介面
/// <summary>
/// 付款介面
/// </summary>
public interface IPayment
{
string Pay(int amount);
}
現金付款 & ApplePay 付款實作
/// <summary>
/// 實作以現金付款
/// </summary>
public class Cash : IPayment
{
public string Pay(int amount)
{
return $"使用 現金 付款 {amount} 元";
}
}
/// <summary>
/// 實作以 ApplePay 付款
/// </summary>
public class ApplePay : IPayment
{
public string Pay(int amount)
{
return $"使用 ApplePay 付款 {amount} 元";
}
}
工廠介面
/// <summary>
/// 工廠介面
/// </summary>
public interface ICreator_Payment
{
public IPayment FactoryMethod();
}
實作工廠方法回傳付款實體
/// <summary>
/// 實作工廠介面回傳現金付款實體
/// </summary>
public class Creator_Cash : ICreator_Payment
{
public IPayment FactoryMethod()
{
return new Cash();
}
}
/// <summary>
/// 實作工廠介面回傳 ApplePay 付款實體
/// </summary>
public class Creator_ApplePay : ICreator_Payment
{
public IPayment FactoryMethod()
{
return new ApplePay();
}
}
- 透過
creator.FactoryMethod()
取得付款實體
static void Main(string[] args)
{
var creators = new List<ICreator_Payment>
{
new Creator_Cash(),
new Creator_ApplePay()
};
foreach (var creator in creators)
{
IPayment payment = creator.FactoryMethod();
Console.WriteLine($"{payment.Pay(10)}");
}
Console.ReadLine();
}
執行結果
使用 現金 付款 10 元
使用 ApplePay 付款 10 元
5. Simple Factory
簡單工廠方法屬於工廠方法的特例,並不包含在四人幫(Gang of Four, GoF)的設計模式之中
同樣使用上面付款的例子,不過將取得付款實體的方式改為靜態方法以 Switch 或是 if/else 來實作 Simple Factory
實作簡單工廠方法回傳付款實體
/// <summary>
/// 簡單工廠方法
/// </summary>
/// <param name="payType"></param>
/// <returns></returns>
public static IPayment GetPayment(PayType payType)
{
return payType switch
{
PayType.Cash => new Cash(),
PayType.ApplePay => new ApplePay(),
_ => null
};
}
- 透過
SimpleFactory.GetPayment()
取得付款實體
static void Main(string[] args)
{
foreach (SimpleFactory.PayType payType in Enum.GetValues(typeof(SimpleFactory.PayType)))
{
SimpleFactory.IPayment payment = SimpleFactory.GetPayment(payType);
Console.WriteLine($"{payment.Pay(10)}");
}
Console.ReadLine();
}
執行結果
使用 現金 付款 10 元
使用 ApplePay 付款 10 元
完整程式碼
GitHub:Creational_01_FactoryMethod