設計模式——抽象工廠模式


目錄

shanzm-2020年5月1日 23:20:41

1. 模式簡介

抽象工廠模式(Abstract Factory Pattern):為創建一組相關或相互依賴的對象提供一個接口,而且無須指定它們的具體類。

產品族(產品系列)同一個具體工廠創建的不同等級的產品稱為同一產品族,或是稱為同一產品系列。

注意:同一個產品族的產品是繼承於不同的產品抽象類

在抽象工廠模式中有產品族的概念,而在工廠方法模式中是沒有這個概念的,因為工廠方法模式中一個具體工廠只創建一種具體產品。

產品等級又稱為產品系列,指的是繼承與同一個抽象產品類的所有具體產品稱之為同一個產品等級

為了方便理解產品族和產品等級,舉一個小栗子:

04抽象工廠模式-產品族和產品系列

抽象工廠模式主要類:

  • AbstractProductA抽象產品A類(或是接口),派生出所有的具體產品類ConcreteProductA1、ConcreteProductA2 ……

  • AbstractProductB抽象產品B類(或是接口),派生出所有的具體產品類ConcreteProductB1、ConcreteProductB2 ……

  • AbstractFactory 抽象工廠接口,所有的具體工廠類都是實現該接口

  • ConcreteFactory1 具體工廠1,實現了IFactory接口,創建具體的產品對象ConcreteProductA1和ConcreteProductB1

  • ConcreteFactory2 具體工廠2,實現了IFactory接口,創建具體的產品對象ConcreteProductA2和ConcreteProductB2

注意: 兩個抽象產品類可以有關系,比如:共同繼承或實現一個抽象類或接口

抽象工廠模式的UML:

Abstract Factory Pattern UML

注:原圖片來自《設計模式實訓教程-第二版》

仔細查看UML,可以發現:當系統中只存在一個產品等級時,抽象工廠模式將退化到工廠方法模式。

2. 示例1-使用工廠模式實現對不同數據庫的操作

2.1 背景說明

在實際開發中,有可能會出現更換不同的數據庫,或是一個項目就使用多個類型的數據庫。
所以為便於更換不同的數據庫,我們使用工廠模式,定義不同的具體工廠創建不同數據庫的操作類

示例來源《大話設計模式》,假設某個項目同時具體MSSQL數據庫和Oracle數據庫,兩個數據庫只是類型不同,其中的表以及表的字段都是一樣的。

我們需要對兩個數據庫中的User表進行操作。

按照工廠模式的設計思路,依次實現以下接口和類:

抽象產品:IUserService-聲明查詢和添加User表數據的方法
具體產品:MSSQLUserServiceOracleUserService-分別針對MSSQ和Oracle數據庫的實現IUserService接口
抽象工廠:IDatabaseFactory-聲明創建IUserService對象的方法
具體工廠:MSSQLFactoryOracleFactory-實現IDatabaseFactory接口,分別創建MSSQLUserService對象和OracleUserService對象

2.2 代碼實現

①創建User類

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

②創建產品總接口IUserService和具體產品MSSQLUserService、OracleUserService

//抽象產品
public interface IUserService
{
    void Insert(User user);
    User GetUser(int id);
}

//具體產品:模擬對MSSQL數據庫中的User表的查詢和添加操作
public class MSSQLUserService : IUserService
{
    public void Insert(User user)
    {
        Console.WriteLine($"MSSQL數據庫User表中中-添加新的用戶,Id:{user.Id },Name{user.Name}");
    }
    public User GetUser(int id)
    {
        Console.WriteLine($"MSSQL數據庫User表中-查詢到用戶,Id:{id}");
        return null;
    }
}

//具體產品:模擬對Oracle數據庫中的User表的查詢和添加操作
public class OracleUserService : IUserService
{
    public void Insert(User user)
    {
        Console.WriteLine($"Oracle數據庫User表中-添加新的用戶,Id:{user.Id },Name{user.Name}");
    }
    public User GetUser(int id)
    {
        Console.WriteLine($"Oracle數據庫User表中-查詢到用戶,Id:{id}");
        return null;
    }
}

③創建抽象工廠IDatabaseFactory和具體工廠MSSQLFactory、OracelFactory

//抽象工廠
public interface IDatabaseFactory
{
    IUserService CreateUserService();
}

//具體工廠:創建MSSQLUserService對象
public class MSSQLFactory : IDatabaseFactory
{
    public IUserService CreateUserService()
    {
        return new MSSQLUserService();
    }
}

//具體工廠:創建OracleUserService對象
public class OracleFactory : IDatabaseFactory
{
    public IUserService CreateUserService()
    {
        return new OracleUserService();
    }
}

④客戶端調用

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };

    IDatabaseFactory msSQlFactory = new MSSQLFactory();
    IDatabaseFactory oracleFactory = new OracleFactory();

    //若是對MSSQL數據庫中的User表操作
    IUserService msUserService = msSQlFactory.CreateUserService();
    msUserService.Insert(user);
    msUserService.GetUser(00001);//print:查詢到用戶,Id:00001

    //若是對Oracle數據庫中User表操作
    IUserService oracleUserService = oracleFactory.CreateUserService();
    oracleUserService.Insert(user);
    oracleUserService.GetUser(00001);//print:查詢到用戶,Id:00001
}

2.3 程序類圖

shanzm_抽象工廠模式_示例1



3. 示例2-多數據庫且多表操作

3.1 背景說明

在示例1中,有兩個不同的數據庫,每個數據庫中都有一張User表,我們實現了對每個數據庫的User表查詢和添加數據

我們使用了工廠方法模式,即有一個抽象產品接口(IUserService),有2個具體產品類(MSSQLUserServiceOracleUserService)實現該接口。

有一個抽象工廠接口(IDatabaseFactory),有兩個具體產品工廠類(MSSQLFactoryOracleFactory)實現該接口。

而現在,若是在兩個數據庫中還有一個部門表Department表,需要對Department表操作。

則需要按照以下修改和添加代碼:

  • 添加一個抽象產品接口(IDepService),和實現該接口的有兩個具體產品類(MSSQLDepServiceOracleDepService)。

  • 在原有的抽象工廠接口和具體工廠類中添加創建MSSQLDepService對象和OracleDepService對象的方法。注意工廠方法是在原有的工廠中進行擴展。

3.2 代碼實現

①在示例1的基礎上,添加一個Department類

public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
}

②添加一個新的抽象產品接口(IDepService),和實現該接口的有兩個具體產品(MSSQLDepServiceOracleDepService

//抽象產品
public interface IDepartmentService
{
    void Insert(Department dep);
    Department GetDepartment(int id);
}

//具體產品:模擬對MSSQL數據庫中的Department表的查詢和添加操作
public class MSSQLDepService : IDepartmentService
{
    public Department GetDepartment(int id)
    {
        Console.WriteLine($"MSSQL數據庫的Department表中-查詢到部門,Id:{id}");
        return null;
    }
    public void Insert(Department dep)
    {
        Console.WriteLine($"MSSQL數據庫的Department表中-添加新的部門,Id:{dep.Id }Name:{dep.Name}");
    }
}

//具體產品:模擬對Oracle數據庫中的Department表的查詢和添加操作
class OracleDepService : IDepartmentService
{
    public Department GetDepartment(int id)
    {
        Console.WriteLine($"Oracle數據庫的Department表中-查詢到部門,Id:{id}");
        return null;
    }
    public void Insert(Department dep)
    {
        Console.WriteLine($"Oracle數據庫的Department表中-添加新的部門,Id:{dep.Id }Name:{dep.Name}");
    }
}

③在示例1的基礎上,在原有的抽象工廠接口和具體工廠類中添加創建MSSQLDepService對象和OracleDepService對象的方法

public interface IDatabaseFactory
{
    IUserService CreateUserService();
    IDepartmentService CreateDepService();//在接口中添加新的方法
}

public class MSSQLFactory : IDatabaseFactory
{
    public IDepartmentService CreateDepService()
    {
        return new MSSQLDepService();
    }
    public IUserService CreateUserService()
    {
        return new MSSQLUserService();
    }
}

public class OracleFactory : IDatabaseFactory
{
    public IDepartmentService CreateDepService()
    {
        return new OracleDepService();
    }
    public IUserService CreateUserService()
    {
        return new OracleUserService();
    }
}

④在客戶端調用

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };
   
    IDatabaseFactory DatabaseFactory = new MSSQLFactory();

    //對MSSQL數據庫中的User表操作
    IUserService UserService = DatabaseFactory.CreateUserService();
    UserService.Insert(user);
    UserService.GetUser(00001);

    //對MSSQL數據庫中的Del表操作
    IDepartmentService DepService = DatabaseFactory.CreateDepService();
    DepService.Insert(dep);
    DepService.GetDepartment(1000);
    Console.ReadKey();
}

運行結果:

MSSQL數據庫


假如需要改換為Oracle數據庫,則你只需要將創建具體工廠對象new MSSQLFactory()修改為new OracleFactory(),其他的代碼無需修改
static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };
   
    IDatabaseFactory DatabaseFactory = new MSSQLFactory();

    //對MSSQL數據庫中的User表操作
    IUserService UserService = DatabaseFactory.CreateUserService();
    UserService.Insert(user);
    UserService.GetUser(00001);

    //對MSSQL數據庫中的Del表操作
    IDepartmentService DepService = DatabaseFactory.CreateDepService();
    DepService.Insert(dep);
    DepService.GetDepartment(1000);
    Console.ReadKey();
}

運行結果:

MSSQL數據庫

3.3 程序類圖

shanzm_抽象工廠

【說明】:

  • MSSQLUserServiceMSSQLDepService是由同一個具體工廠MSSQLFactory創建的,即二者屬於同一產品族。

  • OracleUserServiceOracleDepService是由同一個具體工廠OracleFactory創建的,即二者屬於同一產品族。

  • 而我們需要切換數據庫的時候(即切換產品族),只需要修改創建具體工廠對象:MSSQLFactory對象或OracleFactory對象。這就是抽象工廠模式的最大優點!



4. 重構示例2-使用簡單工廠改進抽象工廠

上述示例項目中,假如再添加一個新的表Student,添加對該表的操作類,則先需要定義一個抽象接口IStudentService接口,派生針對不同數據庫操作的兩個類:MSSQLStudentServiceOracleStudentService,這之后再在IDatabaseFactory接口中添加一個CreateStudentService()方法,接着在兩個具體的工廠類中實現該接口。

我們可以使用簡單工廠模式實現上述的示例2中的項目:

完整演示Demo代碼下載

①以下接口和類和示例2中一樣
抽象產品A:IUserService ,派生出具體產品:MSSQLUserServiceOracleUserService
抽象產品B:IDepService,派生出具體產品:MSSQLDepServiceOracleDepService

②定義簡單工廠類:
因為這里有兩個抽象產品,所以和之前的一般的簡單工廠不同的地方就是要建立兩個工廠方法:

public class DatabaseFactory
{
    private static readonly string db = "MSSQL";//若是需要更換數據庫則將字符串改"Oracle"

    //針對抽象產品IUserService的工廠方法
    public static IUserService CreateUserService()
    {
        IUserService userService = null;
        switch (db)
        {
            case "MSSQL":
                userService = new MSSQLUserService();
                break;
            case "Oracle":
                userService = new OracleUserService();
                break;
        }
        return userService;
    }

    //針對抽象產品IDepService的工廠方法
    public static IDepartmentService CreateDeprService()
    {
        IDepartmentService depService = null;
        switch (db)
        {
            case "MSSQL":
                depService = new MSSQLDepService();
                break;
            case "Oracle":
                depService = new OracleDepService();
                break;
        }
        return depService;
    }
}

如果需要更換數據庫,則只需要簡單的將 private static readonly string db該字段賦值改為"Oracle"

③客戶端調用

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };

    IUserService userService = DatabaseFactory.CreateUserService();
    userService.Insert(user);

    IDepartmentService depService = DatabaseFactory.CreateDeprService();
    depService.Insert(dep);

    Console.ReadKey();
}

運行結果:

04抽象工廠模式-簡單工廠

【說明】

  • 在這里使用簡單該廠模式對比使用抽象工廠模式,簡化了許多的類和接口,所有的修改都可以在工廠類中進行修改添加

  • 同樣也實現了客戶端和創建實例過程的分離

④程序類圖

使用簡單工廠改進抽象工廠UML

對比抽象工廠模式,只是將所有的抽象工廠和具體工廠全部簡化為一個工廠類,該工廠類中有兩個工廠方法



5. 重構示例2-反射+簡單工廠

通過使用反射我們可以免去在工廠方法中使用switch語句,
通過反射獲取需要創建實例的對象名,然后創建該類的實例對象(本質上就是依賴注入
看上去好像並沒有變得更加方便,但其實是如有產品族比較多的情況下,switch語句的case語句也相應的變多
所以使用反射,可以省略使用switch還是不錯的。

代碼實現,在 4. 重構示例2-使用簡單工廠改進抽象工廠的基礎上,修改工廠類:

完整演示Demo代碼下載

public class DatabaseFactory
{
    //具體產品所在的程序集名稱
    private static readonly string AssemblyName = "04抽象工廠模式-多數據庫連接-反射+簡單工廠";
    private static readonly string db = "MSSQL";//若是需要更換數據庫則將字符串改為"Oracle"

    public static IUserService CreateUserService()
    {
        //具體產品的完全限定名
        //注意因為我們的這個項目中有特殊字符,所以程序集的名字和項目名不一致,查看程序集名和命名空間名可以右鍵項目屬性
        string className = "_04抽象工廠模式_多數據庫連接_反射_簡單工廠" + "." + db + "UserService";
        return (IUserService)Assembly.Load(AssemblyName).CreateInstance(className);
      
    }
    public static IDepartmentService CreateDeprService()
    {
        string className = "_04抽象工廠模式_多數據庫連接_反射_簡單工廠" + "." + db + "DepService";
        return (IDepartmentService)Assembly.Load(AssemblyName).CreateInstance(className);
    }
}


6. 重構示例2-反射+配置文件+簡單工廠

5. 重構示例2-反射+簡單工廠若是需更換數據,還是需要修改private static readonly string db = "MSSQL"字段
即任然需要修改代碼后在重新編譯,我們可以將需要修改的字段值放在配置文件中

完整演示Demo代碼下載

修改5. 重構示例2-反射+簡單工廠如下:

①首先本項目添加引用"System.Configuration"

②在配置文件App.Config中添加如下配置

<configuration>
  <appSettings>
    <add key="db" value="MSSQL"/><!--更換數據則<add key="db" value="Oracle"/>-->
  </appSettings>
</configuration>

③修改工廠類中的db字段

private static readonly string db = ConfigurationManager.AppSettings["db"];//db字段的值從配置文件中讀取

【說明】:其實在所有在用到簡單工廠的地方,都可以考慮使用反射技術去除去switch或if,解除分支判斷帶來的耦合



7. 總結分析

整個示例項目,由工廠方法模式-->抽象工廠模式-->簡單工廠模式,你可以仔細的查看三個實現方式的程序類圖,值得琢磨!

7.1 優點

從UML類圖中就可以發現:

  1. 便於交換產品系列,每一個具體的工廠對象都只是在客戶端中初始化時候實現一次,所以改變一個具體的工廠對象是十分簡單的,所以更換一個產品序列也就變得簡單了。

    簡單的說,就是因為具體產品都是由具體的工廠創建的,所以在更換產品族的時候只需要簡單的修改具體工廠對象即可

  2. 創建具體產品對象的過程和客戶端分離(可以從UML中明顯看出),客戶端通過操作抽象產品接口實現操作具體產品實例,具體產品的類名不會出現在客戶端中。

7.2 缺點

  1. 添加新的產品族是非常簡單的,首先在相應的產品等級結構中添加新的具體產品,然后添加一個具體工廠即可。

  2. 添加新的產品等級是非常麻煩的,首先要添加抽象產品接口,接着派生所有的具體產品,還要在抽象工廠中添加方法,以及所有的具體工廠中實現該方法。

對比以上就明白:
抽象工廠模式的擴展有一定的“開閉原則”傾斜性
當增加一個新的產品族時只需增加一個新的具體工廠,不需要修改原代碼,滿足開閉原則。
當增加一個新的產品等級時,則所有的工廠類都需要進行修改,不滿足開閉原則。

7.3 適應場合

系統中有多個產品族,但每次只使用其中的某一族產品。切換產品族只需要修改一下具體工廠對象即可。

比如本文示例中,針對不同數據庫操作我們可以實現不同的產品族,切換數據庫只需要簡單的修改具體工廠對象。



8. 參考及源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM