Factory Method Pattern 工廠方法模式簡介與 C# 範例【建立型】【設計模式來了】

2023-05-29 18:02:08

〇、簡介

1、什麼是工廠方法模式?

一句話解釋:

  實體類和工廠類均為單獨實現,不影響已實現的類,方便擴充套件。

工廠方法模式(Factory Method Pattern)是一種建立型模式,它允許使用者端通過工廠方法來建立物件,而不是直接使用建構函式。這樣可以讓使用者端程式碼更加靈活,同時保持實現的獨立性。工廠方法可以返回同一類的物件,也可以返回不同的類物件。

 一個比喻:(班主任與班級)

  一個班級對應一個班主任,當插班生超額時,則需要重新組成一個班級,那麼此時就需要完成一個班級的各種條件,比如一個新的教室、班主任、班級編號等,當然這個新增的班級對原有的多個班級無影響。

2、優缺點和使用場景

優點:

  • 工廠方法模式將物件的建立過程封裝到了具體的工廠類中,使得使用者端無需知道實際建立的具體物件是哪個類,降低了程式的耦合度
  • 通過定義抽象工廠類和具體工廠類,可以輕鬆新增新的產品型別,同時不影響現有程式碼的功能
  • 工廠方法模式可以適應開閉原則,即對擴充套件開放,對修改關閉。當需要增加新的產品時,只需要新增具體產品類和對應的具體工廠類即可,不必修改現有類的程式碼。
  • 工廠方法模式提高了程式碼的可測試性,因為可以通過依賴注入的方式來模擬工廠物件並建立所需的產品物件。

缺點:

  • 增加複雜性:工廠方法模式引入了額外的抽象層次,使得程式碼結構變得更加複雜,需要編寫更多的程式碼。
  • 在增加新產品型別時,可能需要新增大量的新類和程式碼,導致系統可維護性降低。

總之,工廠方法模式是一種常用且有效的設計模式,可以提高程式的靈活性和可延伸性,但也需要在實際開發中根據具體情況進行權衡和應用。

 使用場景舉例:

  • 可以用於物件的建立過程比較複雜,需要建立不同型別的物件時,將建立過程封裝起來,提高程式碼的可維護性和靈活性。
  • 可以用於不確定物件的類別、個數或者依賴關係的情況。
  • 可以用於需要重複使用的物件(例如資源池等)的情況。
  • 可以用於測試驅動開發的框架下。

一、工廠方法模式簡單實現

如下範例,先定義產品介面 IProduct 和工廠介面 IFactory,再分別進行實現:

#region 定義介面
interface IProduct
{
    void Use();
}
// 定義工廠介面  
interface IFactory
{
    IProduct CreateProduct();
}
#endregion

#region 產品 A
// 實現產品介面的具體產品類  
class ConcreteProductA : IProduct
{
    public void Use()
    {
        Console.WriteLine("使用具體產品 A");
    }
}
// 實現工廠介面的具體工廠類  
class ConcreteFactoryA : IFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductA();
    }
}
#endregion

#region 產品 B
class ConcreteProductB : IProduct
{
    public void Use()
    {
        Console.WriteLine("使用具體產品 B");
    }
}
class ConcreteFactoryB : IFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductB();
    }
}
#endregion

下邊進行實際的呼叫:

// 工廠方法模式
IFactory factoryA = new ConcreteFactoryA();
IProduct productA1 = factoryA.CreateProduct(); // 將物件的建立進行了封裝
IProduct productA2 = factoryA.CreateProduct();
productA1.Use();
productA2.Use();
IFactory factoryB = new ConcreteFactoryB();
IProduct productB = factoryB.CreateProduct();
productB.Use();
// 不用工廠方法模式的寫法
ConcreteProductA concreteProductA1 = new ConcreteProductA(); // 直接建立物件
concreteProductA1.Use();
ConcreteProductA concreteProductA2 = new ConcreteProductA();
concreteProductA2.Use();
ConcreteProductB concreteProductB = new ConcreteProductB();
concreteProductB.Use();

由以上程式碼可見:通過工廠方法模式的實現,將物件的建立進行了封裝,程式碼邏輯更清晰易讀,當需要新增、刪除或修改某個產品時,只需修改具體工廠類即可,既方便又安全。

  

二、在 .NET 框架中的一個實際應用

依賴注入(Dependency Injection,簡稱DI)實際上是工廠方法模式的一種變體,通常與工廠方法模式一起使用。依賴注入可以幫助我們實現物件的建立和管理,使得物件的建立過程更加靈活和可控。

下面一個範例,在建立了語言介面 ILanguage、語言工廠抽象類 LanguageFactory 並實現後,又建立了一個 ClientCode 類來封裝使用者端程式碼的實現,並使用建構函式將具體工廠類作為引數傳遞給該類。使用者端程式碼可以通過呼叫 Speak() 方法來建立相應的產品物件,並執行相關方法。

// 抽象語言類
public interface ILanguage {
   void Speak();
}
// 英語類
public class English : ILanguage {
   public void Speak() {
       Console.WriteLine("Speak English.");
   }
}
// 中文類
public class Chinese : ILanguage {
   public void Speak() {
       Console.WriteLine("說中文。");
   }
}
// 工廠方法抽象類
public abstract class LanguageFactory {
   public abstract ILanguage CreateLanguage();
}
// 英語工廠
public class EnglishFactory : LanguageFactory {
   public override ILanguage CreateLanguage() {
       return new English();
   }
}
// 中文工廠
public class ChineseFactory : LanguageFactory {
   public override ILanguage CreateLanguage() {
       return new Chinese();
   }
}
// 使用者端程式碼
public class ClientCode {
   private readonly LanguageFactory _factory;
   public ClientCode(LanguageFactory factory) { // 依賴注入 LanguageFactory
       _factory = factory;
   }
   public void Speak() {
       ILanguage language = _factory.CreateLanguage();
       language.Speak();
   }
}
// 使用範例
static void Main(string[] args) 
{   
   ClientCode code1 = new ClientCode(new EnglishFactory()); // 依賴注入:英語工廠
   code1.Speak(); // Speak English.
   
   ClientCode code2 = new ClientCode(new ChineseFactory()); // 依賴注入:中文工廠
   code2.Speak(); // 說中文。
}

當然,如果後續有擴充套件需求,比如再新建一個俄語實現:

(只需增加兩個獨立的實現:語言介面 ILanguage、語言工廠抽象類 LanguageFactory)

// 俄語類
public class Russian : ILanguage
{
    public void Speak()
    {
        Console.WriteLine("По-русски.");
    }
}
// 俄語工廠
public class RussianFactory : LanguageFactory
{
    public override ILanguage CreateLanguage()
    {
        return new Russian();
    }
}

然後直接使用,注入俄語工廠new RussianFactory()即可輸出俄語,而對原有的實現沒有任何影響。

ClientCode code3 = new ClientCode(new RussianFactory()); // 依賴注入:俄語工廠
code3.Speak();

實際輸出,見第三行:

  

實際上在 .net 框架中,還有許多類似的用法,例如:

  • 在 ASP.NET MVC 中,可以使用 HtmlHelper 的擴充套件方法建立各種型別的 HTML 表單控制元件,如文字方塊、下拉框等,這些方法內部呼叫控制元件工廠來建立相應的控制元件物件。
  • 在 log4net 框架中,可以使用 LogManager 類的 GetLogger() 方法來獲取或建立指定名稱的記錄器物件。
  • 使用 ADO.NET 進行資料庫操作時,可以使用 DbProviderFactory 類作為通用的提供者工廠類,用來建立特定資料庫提供程式的物件。通過呼叫 DbProviderFactories.GetFactory() 方法就可以獲取該工廠類,並利用其建立具體的資料庫連線、命令、資料介面卡等物件。

方式都是類似的,詳情自行檢視原始碼吧。