# 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  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/) --- ## 新手上路,若有錯誤還請告知,謝謝
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up