2. Factory Method


Posted by WayneCheng on 2021-01-10

關於 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();
    }
}
  1. 透過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();
    }
}
  1. 透過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
           };
}
  1. 透過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


總結

本系列將 Factory Method 放在第一篇主要是因為想將 Factory Method 與 Abstract Factory 接連著說明,且由 Factory Method 開始理解起來較為容易,所以並未照著 Design Patterns 書中的順序。

希望能藉由情境說明的方式加深對於 Design Pattern 的理解 & 印象,且能在遇到類似需求時能聯想到併套用於實際開發中。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

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


#designpattern #CSharp







Related Posts

[27-1] 強制轉型 - 番外篇 ( 運算子預設的規定 ex: ==、+ )

[27-1] 強制轉型 - 番外篇 ( 運算子預設的規定 ex: ==、+ )

[Release Notes] 20200918_v1 - Fix blog like/bookmark button

[Release Notes] 20200918_v1 - Fix blog like/bookmark button

MAC PHP執行發生錯誤 在 Chrome 上顯示 Error 500

MAC PHP執行發生錯誤 在 Chrome 上顯示 Error 500


Comments