---
# System prepended metadata

title: 2. Factory Method
tags: [DesignPatterns]

---

# 2. Factory Method

###### tags: `DesignPatterns`

## 關於 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](https://en.wikipedia.org/wiki/Design_Patterns)

- 定義用來創建物件的介面，但實例化哪個類別由子類別決定
- 工廠方法將實例化延遲至子類別

    Factory Method(工廠方法)屬於創建型(Creational Patterns)，當遇到需要**依靠多個 if/else 來判斷創建哪個實體**時，使用 Factory Method 來將依據需求取得相對應實體的這個部分抽象化，由於外部不需要了解取得實體的細節，使得相依減低，進而達到高內聚低耦合的目的。

優點：
- 符合 單一職責原則(Single Responsibility Principle) 
- 符合 開閉原則(Open Closed Principle) (Simple Factory 不符合)

缺點：
- 會增加許多 class 造成程式複雜度增加

---
## 2. UML
![](https://i.imgur.com/VcEuSCx.jpg)
Class 間關聯：
- ConcreteProduct 繼承 Product
- ConcreteCreator 繼承 Creator
- ConcreteCreator 依賴 ConcreteProduct

Class：
- Product：處理邏輯的抽象類別或介面
- ConcreteProduct：處理邏輯的實作
- Creator：工廠方法的抽象類別或介面
- ConcreteCreator：工廠方法的實作，用來創建處理邏輯的實體

---
## 3. 將 UML 轉為程式碼
開始之前先說明一下，UML 上雖然是以子類別繼承父類別來標示關聯，但範例中沒有多層繼承關係，且平常 interface 使用 DI 較為方便，C# 8.0 已支援預設實作([MSDN](https://docs.microsoft.com/zh-tw/dotnet/csharp/tutorials/default-interface-methods-versions))，所以在**範例程式碼的撰寫上都會盡量以 interface 取代 abstract class**。

ConcreteProduct A / B 皆實作 IProduct 介面
```C#
/// <summary>
/// 處理邏輯的介面
/// </summary>
public interface IProduct
{
}

/// <summary>
/// 處理邏輯的實作 A
/// </summary>
public class ConcreteProductA : IProduct
{
}

/// <summary>
/// 處理邏輯的實作 B
/// </summary>
public class ConcreteProductB : IProduct
{
}
```
工廠方法的介面
```C#
/// <summary>
/// 工廠方法的介面
/// </summary>
public interface ICreator
{
    public IProduct FactoryMethod();
}
```
ConcreteCreator A / B 皆實作 ICreator 介面
```C#
/// <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`實體

```C#
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();
}
```
執行結果
```Console
Created ConcreteProductA
Created ConcreteProductB
```

---
## 4. 情境
我們接到了一個付款的需求
- 需要能支援現有的兩種付款方式(現金、ApplePay)
- 且未來可能會有更多不同的付款方式

付款介面
```C#
/// <summary>
/// 付款介面
/// </summary>
public interface IPayment
{
    string Pay(int amount);
}
```
現金付款 & ApplePay 付款實作
```C#
/// <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} 元";
    }
}
```
工廠介面
```C#
/// <summary>
/// 工廠介面
/// </summary>
public interface ICreator_Payment
{
    public IPayment FactoryMethod();
}
```
實作工廠方法回傳付款實體
```C#
/// <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()`取得付款實體

```C#
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();
}
```
執行結果
```Console
使用 現金 付款 10 元
使用 ApplePay 付款 10 元
```

---
## 5. Simple Factory
簡單工廠方法屬於工廠方法的特例，並不包含在四人幫(Gang of Four, GoF)的設計模式之中
同樣使用上面付款的例子，不過將取得付款實體的方式改為靜態方法以 Switch 或是 if/else 來實作 Simple Factory

實作簡單工廠方法回傳付款實體
```C#
/// <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()`取得付款實體

```C#
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();
}
```
執行結果
```Console
使用 現金 付款 10 元
使用 ApplePay 付款 10 元
```

---
## 完整程式碼
GitHub：[Creational_01_FactoryMethod](https://github.com/darionnnnnn/blog/tree/master/Blog/Creational_01_FactoryMethod)
   
---
## 總結
### 本系列將 Factory Method 放在第一篇主要是因為想將 Factory Method 與 Abstract Factory 接連著說明，且由 Factory Method 開始理解起來較為容易，所以並未照著 [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 書中的順序。

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

---
## 參考資料
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/)

---

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