Home [閱讀筆記][Design Pattern] Ch15.抽象工廠模式(Abstract Factory)
Post
Cancel

[閱讀筆記][Design Pattern] Ch15.抽象工廠模式(Abstract Factory)

資料庫存取

  • 用工廠方法模式
  • 用抽象工廠模式
  • 用簡單工廠來改進抽象工廠
  • 用反射+抽象工廠
  • 用反射+設定檔

設計模式中的工廠模式(Factory Design pattern)是一個比較常用的創建型設計模式,其中可以細分為三種:簡單工廠(Simple Factory)、工廠方法(Factory Method)和抽象工廠(Abstract Factory)。那麼三者有什麼差別呢?

  • 簡單工廠:只有唯一工廠(簡單工廠),一個產品介面/抽象類,根據簡單工廠中的靜態方法來創建具體產品物件。適用於產品較少,幾乎不擴展的情景
  • 工廠方法:有多個工廠(抽象工廠+多個具體工廠),一個產品介面/抽象類,根據繼承抽象工廠中的方法來多態建立具體產品物件。適用於一個類型的多個產品
  • 抽象方法:有多個工廠(抽象工廠+多個特定工廠),多個產品介面/抽象類,將產品子類別分組,根據繼承抽象工廠中的方法多態建立同組的不同特定產品物件。適用於多個類型的多個產品

抽象工廠模式(Abstract Factory)

抽象工廠模式(Abstract Factory),提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類別。

結構

  • AbstractProductAAbstractProductB是兩個抽象產品,之所以為抽象,是因為它們都有可能有兩種不同的實現。
  • ProductA1ProductA2ProductB1ProductB2就是對兩個抽象產品的具體分類的實現。
  • IFactory是一個抽象工廠介面,它裡面應該包含所有產品建立的抽象方法。
  • ConcreteFactory1ConcreteFactory2就是具體工廠,通常是在執行時再建立一個ConcreteFactory類別的實體,這個具體的工廠再建立具有特定實現的產品物件,也就是說,為建立不同的產品物件,用戶端應使用不同的具體工廠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Client
    AbstractFactory 抽象工廠介面,它裡面應該包含所有產品建立的抽象方法
    + CreateProductA()
    + CreateProductB()

        ConcreteFactory1 具體的工廠,建立具有特定實現的產品物件
        ConcreteFactory1

    AbstractProductA 抽象產品,它們都有可能有兩種不同的實現
        ProductA1 對兩個抽象產品的具體分類實現
        ProductA2
    AbstractProductB
        ProductB1
        ProductB2

優缺點

優點:

第一:最大的好處是易於交換產品系列,由於具體工廠類別,例如:IFactory factory = new SqlServerFactory();,在一個應用中只需要初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工作即可使用不同的產品設定。

我們的設計不能去防止需求的更改,那麼我們的理想便是讓改動變得更小,現在如果要更改資料庫存取,只需要更改具體工廠就可以做到。

第二:它讓具體的建立實體過程與用戶端分離,用戶端是透過它們的抽象介面操縱實體,產品的具體類別名也被具體工廠的實現分離,不會出現在客戶程式碼中。

缺點:

抽象工廠模式可以很方便地切換兩個資料庫存取的程式碼,但是,如果需求來自「增加功能」,那至少要增加三個類別:IProductSqlServerProdjectAccessProject,還需要更改三個類別IFactorySqlServerFactoryAccessFactory,這太糟糕了。

用戶端程式類別顯然不會只有一個,如果有100個調用資料庫存取的類別,那就要改100次了,這不能解決要更改資料庫存取時,改動一處就完全更改的要求。

這樣大批量的改動,顯然是非常醜陋的做法。

最基本的資料存取程式

假設用戶類別只有ID和Name兩個欄位,其餘省略

1
2
3
4
class User {
    public int ID { get; set; }
    public string name { get;set; }
}

SqlServerUser類別,用於操作User表,假設只有「新增用戶」和「取得用戶」方法,其餘方法以及具體的SQL敘述省略。

1
2
3
4
5
6
7
8
9
10
11
class SqlServerUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在SQL Server中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得User 表一筆記錄");
        return null;
    }
}

用戶端程式碼

1
2
3
4
User user = new User();
SqlServerUser su = new SqlServerUser();
su.InsertUser(user); //新增用戶
su.GetUserById(1); //取得用戶ID為1的資料

用工廠方法模式的資料存取

工廠方法模式是定義一個用於建立物件的介面,讓子類別決定實體化哪一個類別。
用「工廠方法模式」來封裝new SqlServerUser()所造成的變化。

工廠方法:有多個工廠(抽象工廠+多個具體工廠),一個產品介面/抽象類,根據繼承抽象工廠中的方法來多態建立具體產品物件。適用於一個類型的多個產品。

結構

1
2
3
4
5
6
7
8
9
10
11
IUser
    SqlServerUser
    AccessUser

IFactory
+ CreateUser()

    SqlServerFactory
    + CreateUser()
    AccessFactory
    + CreateUser()

程式碼

IUser介面

  • IUser介面,用於用戶端存取,解除與具體資料存取的耦合
  • SqlServerUser類別,用於存取SqlServer的User
  • AccessUser類別,用於存取Access的User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//IUser介面,用於用戶端存取,解除與具體資料存取的耦合
interface IUser {
    void InsertUser(User user);
    User GetUserById(int id);
}
//SqlServerUser類別,用於存取SqlServer的User
class SqlServerUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在SQL Server中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得User 表一筆記錄");
        return null;
    }
}
//AccessUser類別,用於存取Access的User
class AccessUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在Access中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在Access中根據ID取得User 表一筆記錄");
        return null;
    }
}

IFactory介面

  • IFactory介面,定義一個建立存取User表物件的抽象的工廠介面
  • SqlServerFactory類別,實現IFactory介面,實體化SqlServerUser
  • AccessFactory類別,實現IFactory介面,實體化AccessUser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//IFactory介面,定義一個建立存取User表物件的抽象的工廠介面
interface IFactory {
    IUser CreateUser();
}
//SqlServerFactory類別,實現IFactory介面,實體化SqlServerUser
class SqlServerFactory {
    public IUser CreateUser() {
        return new SqlServerUser();
    }
}
//AccessFactory類別,實現IFactory介面,實體化AccessUser
class AccessFactory {
    public IUser CreateUser() {
        return new AccessUser();
    }
}

用戶端程式碼

如果要更改db,只要把new SqlServerFactory();改成new AccessFactory();
由於多型的關係,使得宣告IUser介面的物件user事先根本不知道是誰在存取哪個資料庫,卻可以在執行時順利地完成工作,這就是所謂的業務邏輯與資料存取的解耦。

1
2
3
4
5
6
7
8
9
User user = new User();

IFactory factory = new SqlServerFactory(); //SqlServer資料庫
//要更改Access資料庫,只要將本句改成:
//IFactory factory = new AccessFactory(); //Access資料庫

IUser iuser = factory.CreateUser();
iuser.InsertUser(user);
iuser.GetUserById(1);

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//User表
class User {
    public int ID { get; set; }
    public string name { get;set; }
}

//IUser介面,用於用戶端存取,解除與具體資料存取的耦合
interface IUser {
    void InsertUser(User user);
    User GetUserById(int id);
}

//實作IUser介面:SqlServerUser類別,用於存取SqlServer的User
class SqlServerUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在SQL Server中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得User 表一筆記錄");
        return null;
    }
}
//實作IUser介面:AccessUser類別,用於存取Access的User
class AccessUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在Access中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在Access中根據ID取得User 表一筆記錄");
        return null;
    }
}

//IFactory介面,定義一個建立存取User表物件的抽象的工廠介面
interface IFactory {
    IUser CreateUser();
}
//實作IFactory介面:SqlServerFactory類別,實現IFactory介面,實體化SqlServerUser
class SqlServerFactory:IFactory {
    public IUser CreateUser() {
        return new SqlServerUser();
    }
}
//實作IFactory介面:AccessFactory類別,實現IFactory介面,實體化AccessUser
class AccessFactory:IFactory {
    public IUser CreateUser() {
        return new AccessUser();
    }
}

//用戶端調用
public static void Main()
{
    User user = new User();

    IFactory factory = new SqlServerFactory(); //SqlServer資料庫
    //要更改Access資料庫,只要將本句改成:
    //IFactory factory = new AccessFactory(); //Access資料庫

    IUser iuser = factory.CreateUser();
    iuser.InsertUser(user);
    iuser.GetUserById(1);
}

用抽象工廠方法模式(增加部門表)

資料庫裡不可能只有一個User表,還有很多其他表,比如:部門表Department。

1
2
3
4
class Department {
    public int ID {get; set; }
    public string DeptName{ get; set; }
}

抽象方法:有多個工廠(抽象工廠+多個特定工廠),多個產品介面/抽象類,將產品子類別分組,根據繼承抽象工廠中的方法多態建立同組的不同特定產品物件。適用於多個類型的多個產品

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IUser
    SqlServerUser
    AccessUser
IDepartment
    SqlServerDepartment
    AccessDepartment

IFactory
+ CreateUser()
+ CreateDepartment()

    SqlServerFactory
    + CreateUser()
    + CreateDepartment()
    AccessFactory
    + CreateUser()
    + CreateDepartment()

程式碼

IDepartment介面

  • IDepartment部門介面,用於用戶端存取,解除與具體資料庫存取的耦合
  • SqlServerDepartment類別,用於存取SQL Server的Department
  • AccessDepartment類別,用於存取Access的Department
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//IDepartment部門介面,用於用戶端存取,解除與具體資料庫存取的耦合
interface IDepartment {
    void InsertDepartment(Department department);
    Department GetDepartmentById(int id);
}
//SqlServerDepartment類別,用於存取SQL Server的Department
class SqlServerDepartment: IDepartment {
    //新增
    public void InsertDepartment(Department department) {
        Console.WriteLine("在SQL Server中給Department新增一筆記錄");
    }
    //取得
    public Department GetDepartmentById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得Department 表一筆記錄");
        return null;
    }
}
//AccessDepartment類別,用於存取Access的Department
class AccessDepartment: IDepartment {
    //新增
    public void InsertDepartment(Department department) {
        Console.WriteLine("在Access中給Department新增一筆記錄");
    }
    //取得
    public Department GetDepartmentById(int id) {
        Console.WriteLine("在Access中根據ID取得Department 表一筆記錄");
        return null;
    }
}

IFactory介面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//IFactory介面,定義一個建立存取User、Department表物件的抽象的工廠介面
interface IFactory {
    IUser CreateUser();
    IDepartment CreateDepartment(); //增加的介面方法
}

//SqlServerFactory類別,實現IFactory介面,實體化SqlServerUser、SqlServerDepartment
class SqlServerFactory: IFactory {
    public IUser CreateUser() {
        return new SqlServerUser();
    }
    public IDepartment CreateDepartment() {
        return new SqlServerDepartment();
    }
}
//AccessFactory類別,實現IFactory介面,實體化AccessUser、SqlServerDepartment
class AccessFactory: IFactory {
    public IUser CreateUser() {
        return new AccessUser();
    }
    public IDepartment CreateDepartment() {
        return new AccessDepartment();
    }
}

用戶端程式碼

這樣就可以做到,只需要更改IFactory factory = new SqlServerFactory();IFactory factory = new AccessFactory();,就實現了資料庫存取的切換了。

只有一個User類別和User操作類別時,是只需要工廠方法模式的,但現在顯然資料庫有很多的表,而SQL Server和 Access 又是兩大不同的分類,所以解決這種涉及到多個產品系列的問題,有一個專門的工廠模式叫「抽象工廠模式」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
User user = new User();
Department dempartment = new Department();

//切換資料庫:只需要確定實體化哪一個資料庫存取物件給factory
IFactory factory = new SqlServerFactory(); //SqlServer資料庫
//IFactory factory = new AccessFactory(); //Access資料庫

//使用者
IUser iuser = factory.CreateUser();
iuser.InsertUser(user);
iuser.GetUserById(1);

//部門
IDepartment idempartment = factory.CreateDepartment();
idempartment.InsertDepartment(dempartment);
idempartment.GetDepartmentById(1);

//執行結果:
//在SQL Server中給User新增一筆記錄
//在SQL Server中根據ID取得User 表一筆記錄
//在SQL Server中給Department新增一筆記錄
//在SQL Server中根據ID取得Department 表一筆記錄

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//User表
class User {
    public int ID { get; set; }
    public string name { get;set; }
}
//部門表
class Department {
    public int ID {get; set; }
    public string DeptName{ get; set; }
}

//
//IFactory介面,定義一個建立存取User、Department表物件的抽象的工廠介面
interface IFactory {
    IUser CreateUser();
    IDepartment CreateDepartment(); //增加的介面方法
}

//SqlServerFactory類別,實現IFactory介面,實體化SqlServerUser、SqlServerDepartment
class SqlServerFactory: IFactory {
    public IUser CreateUser() {
        return new SqlServerUser();
    }
    public IDepartment CreateDepartment() {
        return new SqlServerDepartment();
    }
}
//AccessFactory類別,實現IFactory介面,實體化AccessUser、SqlServerDepartment
class AccessFactory: IFactory {
    public IUser CreateUser() {
        return new AccessUser();
    }
    public IDepartment CreateDepartment() {
        return new AccessDepartment();
    }
}


//
//IUser介面,用於用戶端存取,解除與具體資料存取的耦合
interface IUser {
    void InsertUser(User user);
    User GetUserById(int id);
}

//實作IUser介面:SqlServerUser類別,用於存取SqlServer的User
class SqlServerUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在SQL Server中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得User 表一筆記錄");
        return null;
    }
}
//實作IUser介面:AccessUser類別,用於存取Access的User
class AccessUser: IUser {
    //新增
    public void InsertUser(User user) {
        Console.WriteLine("在Access中給User新增一筆記錄");
    }
    //取得用戶
    public User GetUserById(int id) {
        Console.WriteLine("在Access中根據ID取得User 表一筆記錄");
        return null;
    }
}

//
//IDepartment部門介面,用於用戶端存取,解除與具體資料庫存取的耦合
interface IDepartment {
    void InsertDepartment(Department department);
    Department GetDepartmentById(int id);
}
//SqlServerDepartment類別,用於存取SQL Server的Department
class SqlServerDepartment: IDepartment {
    //新增
    public void InsertDepartment(Department department) {
        Console.WriteLine("在SQL Server中給Department新增一筆記錄");
    }
    //取得
    public Department GetDepartmentById(int id) {
        Console.WriteLine("在SQL Server中根據ID取得Department 表一筆記錄");
        return null;
    }
}
//AccessDepartment類別,用於存取Access的Department
class AccessDepartment: IDepartment {
    //新增
    public void InsertDepartment(Department department) {
        Console.WriteLine("在Access中給Department新增一筆記錄");
    }
    //取得
    public Department GetDepartmentById(int id) {
        Console.WriteLine("在Access中根據ID取得Department 表一筆記錄");
        return null;
    }
}

//
//用戶端程式碼
public static void Main()
{
    User user = new User();
    Department dempartment = new Department();

    //切換資料庫:只需要確定實體化哪一個資料庫存取物件給factory
    IFactory factory = new SqlServerFactory(); //SqlServer資料庫
    //IFactory factory = new AccessFactory(); //Access資料庫

    //使用者
    IUser iuser = factory.CreateUser();
    iuser.InsertUser(user);
    iuser.GetUserById(1);

    //部門
    IDepartment idempartment = factory.CreateDepartment();
    idempartment.InsertDepartment(dempartment);
    idempartment.GetDepartmentById(1);
}
//執行結果:
//在SQL Server中給User新增一筆記錄
//在SQL Server中根據ID取得User 表一筆記錄
//在SQL Server中給Department新增一筆記錄
//在SQL Server中根據ID取得Department 表一筆記錄

用簡單工廠來改進抽象工廠

簡單工廠:只有唯一工廠(簡單工廠),一個產品介面/抽象類,根據簡單工廠中的靜態方法來創建具體產品物件。適用於產品較少,幾乎不擴展的情景。

用一個簡單模式來實現,去除IFactorySqlServerFactoryAccessFactory三個工廠類別,取而代之的是 DataAccess類別。

但是,需要增加Oracle資料存取,就還要在DataAccess類別中每個方法的 switch 中加 case

結構

1
2
3
4
5
6
7
8
9
10
11
DataAccess
- db:string
+ CreateUser(): IUser
+ CreateDepartment(): IDepartment

    IUser
        SqlServerUser
        AccessUser
    IDepartment
        SqlServerDepartment
        AccessDepartment

程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class DataAccess {
    private static readonly string db = "SqlServer";
    //private static readonly string db = "Access";

    public static IUser CreateUser() {
        IUser result = null;

        //根據db的設定,實體化相應的物件
        switch(db) {
            case "SqlServer":
                result = new SqlServerUser();
                break;
            case "Access":
                result = new AccessUser();
                break;     
        }
        return result;
    }

    public static IDepartment CreateDepartment() {
        IDepartment result = null;
        
        //根據db的設定,實體化相應的物件
        switch(db) {
            case "SqlServer":
                result = new SqlServerDepartment();
                break;
            case "Access":
                result = new AccessDepartment();
                break;     
        }
        return result;
    }
}

//用戶端
User user = new User();
Department department = new Department();

//使用者
IUser iuser = DataAccess.CreateUser();
iuser.insert(user);
iuser.GetUserById(1);

//部門
IDepartment idempartment =  DataAccess.CreateDepartment();
idempartment.InsertDepartment(dempartment);
idempartment.GetDepartmentById(1);

但是,需要增加Oracle資料存取,就還要在DataAccess類別中每個方法的 switch 中加 case

用反射+抽象工廠

1
Assembly.Load("程式集名稱").CreateInstance("命名空間.類別名稱")
1
2
3
4
5
6
7
//常規的寫法
IUser result = new SqlServerUser();

//反射的寫法
using System.Reflection;

IUser result = (IUser)Assembly.Load("抽象工廠模式").CreateInstance("抽象工廠模式.SqlServerUser")

用反射+設定檔

加入一個 App.Config

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8">
<configuration>
    <appSettings>
        <add key="DB" value="SqlServer"/>
    </appSettings>
</configuration>

再參考 System.configuration

1
2
3
using System.Configuration;
//讀取設定檔
private static readonly string db = ConfigurationManager.AppSettings["DB"];
This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch14.觀察者模式(Observer)

[閱讀筆記][Design Pattern] Ch16.狀態模式(State)