Abstract Factory Pattern 抽象工廠模式簡介與 C# 範例【建立型】【設計模式來了】

2023-05-30 21:00:24

〇、簡介

1、什麼是抽象工廠模式?

一句話解釋:

  通過對抽象類和抽象工廠的一組實現,獨立出一系列新的操作,使用者端無需瞭解其邏輯直接存取。

抽象工廠模式(Abstract Factory Pattern)是一種建立型模式。它用於建立一組相關物件的家族。強調的是一組物件之間的共同作業關係,而不是單個物件之間的依賴關係。抽象工廠類負責建立整個家族的物件的生命週期,並隱藏與實現有關的邏輯。

 一個比喻:(科目與課代表)

  語文和數學的課代表和副課代表,都按照抽象方法標準選好了,接下來同樣的通過實現抽象類和介面標準,來選出兩名物理課代表。當然,已經選出來的其他課代表,和本次選舉無關聯。

2、優缺點和使用場景

優點:

  • 可以降低系統中各個物件之間的耦合度。
  • 隔離了具體類的生產,使得客戶並不需要知道什麼被建立。
  • 增加新的具體工廠和產品族很方便,無須修改已有系統,符合「開閉原則」。

缺點:

  • 在增加新的產品方面比較困難,需要修改抽象工廠的介面,這樣會導致所有的具體工廠也需要做出相應的修改。
  • 抽象程度高,可能會導致一些底層實現細節難以控制。

總之,抽象工廠模式能夠有效地封裝物件建立,但是擴充套件產品較為困難。它在軟體開發中被廣泛使用,特別是在跨平臺軟體開發中經常用到,使用時要注意系統物件的特點合理使用。

 使用場景舉例:

  • 遊戲開發:遊戲中可能需要多種角色、武器、敵人等元素,它們之間可能存在關聯性或依賴性,可以使用抽象工廠方法來快速構建遊戲元素。
  • 資料庫存取元件設計:不同資料庫的連線、查詢和資料儲存方式可能存在差異,可以使用抽象工廠方法來建立不同資料庫的存取元件、驅動和對映器。
  • 作業系統介面設計:不同作業系統的介面設計具有不同的特點,可以使用抽象工廠方法來建立不同作業系統下的控制元件。

總之,使用抽象工廠模式,都需要保證物件家族之間高內聚、鬆耦合,使得系統的設計和實現更加靈活和可延伸。

一、抽象工廠模式簡單實現與擴充套件

通過兩個抽象產品類 ProductA/ProductBBBB,實現四個具體產品類;在通過抽象工廠介面 IAbstractFactory,實現兩個具體工廠的產品族 ConcreteFactory1/ConcreteFactory2。最後通過 Client 類注入工廠類的同時,建立產品的不同產品的範例,使使用者端不用瞭解產品如何範例化,可以直接參照。

// 抽象產品類。
public abstract class ProductA
{
    public abstract void OperationA();
}
public abstract class ProductBBBB
{
    public abstract void OperationBBBB();
}

// 具體產品類,其中 ProductA1、ProductA2、ProductB1 和 ProductB2 分別代表不同的產品。
public class ProductA1 : ProductA
{
    public override void OperationA()
    {
        Console.WriteLine("ProductA1's operation.");
    }
}
public class ProductA2 : ProductA
{
    public override void OperationA()
    {
        Console.WriteLine("ProductA2's operation.");
    }
}
public class ProductBBBB1 : ProductBBBB
{
    public override void OperationBBBB()
    {
        Console.WriteLine("ProductBBBB1's operation.");
    }
}
public class ProductBBBB2 : ProductBBBB
{
    public override void OperationBBBB()
    {
        Console.WriteLine("ProductBBBB2's operation.");
    }
}

// 抽象工廠介面,定義了各種不同產品族的生產方法。
public interface IAbstractFactory
{
    ProductA CreateProductA();
    ProductBBBB CreateProductBBBB();
}
// 每個具體工廠都能夠生產特定的產品族。
public class ConcreteFactory1 : IAbstractFactory
{
    public ProductA CreateProductA()
    {
        return new ProductA1();
    }
    public ProductBBBB CreateProductBBBB()
    {
        return new ProductBBBB1();
    }
}
public class ConcreteFactory2 : IAbstractFactory
{
    public ProductA CreateProductA()
    {
        return new ProductA2();
    }
    public ProductBBBB CreateProductBBBB()
    {
        return new ProductBBBB2();
    }
}

// 使用者端程式碼使用抽象工廠來建立各種不同產品族的產品,而無需關心它們的實際實現。
public class Client
{
    private readonly ProductA _productA;
    private readonly ProductBBBB _productBBBB;
    public Client(IAbstractFactory factory)
    {
        _productA = factory.CreateProductA();
        _productBBBB = factory.CreateProductBBBB();
    }
    public void Run()
    {
        _productA.OperationA();
        _productBBBB.OperationBBBB();
    }
}
// 測試
static void Main(string[] args)
{
    Client client = new Client(new ConcreteFactory1());
    client.Run();
    Client client2 = new Client(new ConcreteFactory2());
    client2.Run();
    
    // 輸出:
    // ProductA1's operation.
    // ProductBBBB1's operation.
    // ProductA2's operation.
    // ProductBBBB2's operation.
}

下面我們嘗試擴充套件出來一個新的產品 3:

// 具體產品類
public class ProductA3 : ProductA
{
    public override void OperationA()
    {
        Console.WriteLine("ProductA3's operation.");
    }
}
public class ProductBBBB3 : ProductBBBB
{
    public override void OperationBBBB()
    {
        Console.WriteLine("ProductBBBB3's operation.");
    }
}
// 具體工廠都能夠生產特定的產品族
public class ConcreteFactory3 : IAbstractFactory
{
    public ProductA CreateProductA()
    {
        return new ProductA3();
    }
    public ProductBBBB CreateProductBBBB()
    {
        return new ProductBBBB3();
    }
}

 測試:

static void Main(string[] args)
{
    Client client = new Client(new ConcreteFactory1());
    client.Run();
    Client client2 = new Client(new ConcreteFactory2());
    client2.Run();
    Client client3 = new Client(new ConcreteFactory3());
    client3.Run();
}

  

二、抽象工廠模式在 .net 框架中的實際應用

例如 DbProviderFactory,這個類位於 System.Data.Common.dll 程式集中,該類扮演抽象工廠模式中抽象工廠的角色,原始碼如下:

// System.Data.Common, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Data.Common.DbProviderFactory
using System.Data.Common;
public abstract class DbProviderFactory
{
	private bool? _canCreateDataAdapter;
	private bool? _canCreateCommandBuilder;
	public virtual bool CanCreateDataSourceEnumerator => false;
	public virtual bool CanCreateDataAdapter
	{
		get
		{
			if (!_canCreateDataAdapter.HasValue)
			{
				using DbDataAdapter dbDataAdapter = CreateDataAdapter();
				_canCreateDataAdapter = dbDataAdapter != null;
			}
			return _canCreateDataAdapter.Value;
		}
	}
	public virtual bool CanCreateCommandBuilder
	{
		get
		{
			if (!_canCreateCommandBuilder.HasValue)
			{
				using DbCommandBuilder dbCommandBuilder = CreateCommandBuilder();
				_canCreateCommandBuilder = dbCommandBuilder != null;
			}
			return _canCreateCommandBuilder.Value;
		}
	}
	public virtual DbCommand? CreateCommand()
	{
		return null;
	}
	public virtual DbCommandBuilder? CreateCommandBuilder()
	{
		return null;
	}
	public virtual DbConnection? CreateConnection()
	{
		return null;
	}
	public virtual DbConnectionStringBuilder? CreateConnectionStringBuilder()
	{
		return null;
	}
	public virtual DbDataAdapter? CreateDataAdapter()
	{
		return null;
	}
	public virtual DbParameter? CreateParameter()
	{
		return null;
	}
	public virtual DbDataSourceEnumerator? CreateDataSourceEnumerator()
	{
		return null;
	}
}

下面是 SqlClientFactory.cs,繼承了抽象類 DbProviderFactory,需要注意的是,此為參照程式集,即只包含後設資料,不含可執行程式碼。如何通過工廠模式存取 SQLServer 資料庫,可以參考官網範例: 獲取 DbProviderFactory

點選檢視 SqlClientFactory.cs
 // System.Data.SqlClient, Version=4.6.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Data.SqlClient.SqlClientFactory
using System.Data.Common;
using System.Data.SqlClient;

/// <summary>Represents a set of methods for creating instances of the <see cref="N:System.Data.SqlClient" /> provider's implementation of the data source classes.</summary>
public sealed class SqlClientFactory : DbProviderFactory
{
	/// <summary>Gets an instance of the <see cref="T:System.Data.SqlClient.SqlClientFactory" />. This can be used to retrieve strongly typed data objects.</summary>
	public static readonly SqlClientFactory Instance;

	internal SqlClientFactory()
	{
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbCommand" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbCommand" />.</returns>
	public override DbCommand CreateCommand()
	{
		throw null;
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbCommandBuilder" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbCommandBuilder" />.</returns>
	public override DbCommandBuilder CreateCommandBuilder()
	{
		throw null;
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbConnection" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbConnection" />.</returns>
	public override DbConnection CreateConnection()
	{
		throw null;
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbConnectionStringBuilder" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbConnectionStringBuilder" />.</returns>
	public override DbConnectionStringBuilder CreateConnectionStringBuilder()
	{
		throw null;
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbDataAdapter" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbDataAdapter" />.</returns>
	public override DbDataAdapter CreateDataAdapter()
	{
		throw null;
	}

	/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbParameter" /> instance.</summary>
	/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbParameter" />.</returns>
	public override DbParameter CreateParameter()
	{
		throw null;
	}
}

下面再看一下 Oracle 工廠的實現,完全獨立於其他資料庫的工廠:

點選檢視原始碼 OracleClientFactory.cs
#region 程式集 Oracle.ManagedDataAccess, Version=4.122.21.1, Culture=neutral, PublicKeyToken=89b483f429c47342
// C:\Users\zheng\.nuget\packages\oracle.manageddataaccess\21.10.0\lib\net462\Oracle.ManagedDataAccess.dll
// Decompiled with ICSharpCode.Decompiler 7.1.0.6543
#endregion

using System;
using System.Data.Common;
using System.Security;
using System.Security.Permissions;
using OracleInternal.Common;

namespace Oracle.ManagedDataAccess.Client
{
    public sealed class OracleClientFactory : DbProviderFactory
    {
        public static readonly OracleClientFactory Instance = new OracleClientFactory();

        public override bool CanCreateDataSourceEnumerator => true;

        public override DbCommand CreateCommand()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand);
            }

            try
            {
                return new OracleCommand();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand);
                }
            }
        }

        public override DbCommandBuilder CreateCommandBuilder()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder);
            }

            try
            {
                return new OracleCommandBuilder();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder);
                }
            }
        }

        public override DbConnection CreateConnection()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection);
            }

            try
            {
                return new OracleConnection();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection);
                }
            }
        }

        public override DbConnectionStringBuilder CreateConnectionStringBuilder()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder);
            }

            try
            {
                return new OracleConnectionStringBuilder();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder);
                }
            }
        }

        public override DbDataAdapter CreateDataAdapter()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter);
            }

            try
            {
                return new OracleDataAdapter();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter);
                }
            }
        }

        public override DbDataSourceEnumerator CreateDataSourceEnumerator()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator);
            }

            try
            {
                return new OracleDataSourceEnumerator();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator);
                }
            }
        }

        public override DbParameter CreateParameter()
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter);
            }

            try
            {
                return new OracleParameter();
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter);
                }
            }
        }

        public override CodeAccessPermission CreatePermission(PermissionState state)
        {
            if (ProviderConfig.m_bTraceLevelPublic)
            {
                Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission);
            }

            try
            {
                return new OraclePermission(state);
            }
            catch (Exception ex)
            {
                OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission, ex);
                throw;
            }
            finally
            {
                if (ProviderConfig.m_bTraceLevelPublic)
                {
                    Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission);
                }
            }
        }
    }
}

當然,諸如 Mysql、DB2 等類同。由此可見,當後續新增資料庫時,只需對 DbProviderFactory 抽象工廠進行繼承即可,對已實現的資料工廠毫無影響。