C#设计模式(03) - 抽象工厂模式(Abstract Factory Pattern)

一、引言

上一篇文章我们讲了【工厂方法】模式,它是为了解决【简单工厂】模式所面对的问题,它的问题就是:如果我们增加新的产品,工厂类的方法就要修改本身的代码,增加产品越多,其逻辑越复杂,同时这样的修改也是不符合【开放关闭原则OCP】,对修改代码关闭,对增加代码开放。为了解决【简单工厂】的问题,我们引出了【工厂方法】模式,通过子类化工厂类,解决了工厂类责任的划分,产品和相应的工厂一一对应,符合了OCP。如果我们要设计一套房子,当然我们知道房子是由房顶、地板、窗户、房门组成的,别的组件暂时省略,先设计一套古典风格的房子,再创建一套现代风格的房子,再创建一套欧式风格的房子,这么多套房子,我们该怎么办呢?今天我们要讲的【抽象工厂】模式可以很好的解决多套变化的问题。

二、抽象工厂详细介绍

2.1、动机(Motivate):

在软件系统中,经常面临着”一系统相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种”封装机制”来避免客户程序和这种”多系列具体对象创建工作”的紧耦合?

2.2、意图(Intent):

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 ——《设计模式》GoF

2.3、结构图(Structure)

该图是抽象工厂的UML图,结合抽象工厂的意图、动机和图示来理解该模式,今天我们就以建设房子为例来说明抽象工厂的实现机理。

2.4、模式的组成

可以看出,在抽象工厂模式的结构图有以下角色:

  1. 抽象产品类角色(AbstractProduct):为抽象工厂中相互依赖的每种产品定义抽象接口对象,也可以这样说,有几种产品,就要声明几个抽象角色,每一个抽象产品角色和一种具体的产品相匹配。
  2. 具体产品类(ConcreteProduct):具体产品类实现了抽象产品类,是针对某个具体产品的实现的类型。
  3. 抽象工厂类角色(Abstract Factory):定义了创建一组相互依赖的产品对象的接口操作,每种操作和每种产品一一对应。
  4. 具体工厂类角色(ConcreteFactory):实现抽象类里面的所有抽象接口操作,可以创建某系列具体的产品,这些具体的产品是“抽象产品类角色”的子类。
2.5、抽象工厂的具体代码实现

随着我们年龄的增大,我们也到了结婚的年龄。结婚首要的问题就是房子的问题,假设我有一个很有钱的爸爸,哈哈,有钱可以解决很多问题。作为长子的我,希望能有一套欧式风格的房子,再加上田园风光,此生足矣。我弟弟就不一样了,他想要一套现代样式的房子,如果兄弟姊妹再多年一点,那就有更多的要求了。由于房子由房顶、地板、窗户和房门组成,其他组件暂时省略,有这么多套房子要建设,每套房子的房顶、地板、窗户和房门都是一个体系的,那就让我们看看如何使用【抽象工厂】模式来实现不同房屋的建造。

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/// <summary>
/// 下面以不同系列房屋的建造为例子演示抽象工厂模式
/// 因为每个人的喜好不一样,我喜欢欧式的,我弟弟就喜欢现代的
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 哥哥的欧式风格的房子
AbstractFactory europeanFactory= new EuropeanFactory();
europeanFactory.CreateRoof().Create();
europeanFactory.CreateFloor().Create();
europeanFactory.CreateWindow().Create();
europeanFactory.CreateDoor().Create();


//弟弟的现代风格的房子
AbstractFactory modernizationFactory = new ModernizationFactory();
modernizationFactory.CreateRoof().Create();
modernizationFactory.CreateFloor().Create();
modernizationFactory.CreateWindow().Create();
modernizationFactory.CreateDoor().Create();
Console.Read();
}
}

/// <summary>
/// 抽象工厂类,提供创建不同类型房子的接口
/// </summary>
public abstract class AbstractFactory
{
// 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了房顶、地板、窗户和房门创建接口
public abstract Roof CreateRoof();
public abstract Floor CreateFloor();
public abstract Window CreateWindow();
public abstract Door CreateDoor();
}

/// <summary>
/// 欧式风格房子的工厂,负责创建欧式风格的房子
/// </summary>
public class EuropeanFactory : AbstractFactory
{
// 制作欧式房顶
public override Roof CreateRoof()
{
return new EuropeanRoof();
}

// 制作欧式地板
public override Floor CreateFloor()
{
return new EuropeanFloor();
}

// 制作欧式窗户
public override Window CreateWindow()
{
return new EuropeanWindow();
}

// 制作欧式房门
public override Door CreateDoor()
{
return new EuropeanDoor();
}
}

/// <summary>
/// 现在风格房子的工厂,负责创建现代风格的房子
/// </summary>
public class ModernizationFactory : AbstractFactory
{
// 制作现代房顶
public override Roof CreateRoof()
{
return new ModernizationRoof();
}

// 制作现代地板
public override Floor CreateFloor()
{
return new ModernizationFloor();
}

// 制作现代窗户
public override Window CreateWindow()
{
return new ModernizationWindow();
}

// 制作现代房门
public override Door CreateDoor()
{
return new ModernizationDoor();
}
}

/// <summary>
/// 房顶抽象类,子类的房顶必须继承该类
/// </summary>
public abstract class Roof
{
/// <summary>
/// 创建房顶
/// </summary>
public abstract void Create();
}

/// <summary>
/// 地板抽象类,子类的地板必须继承该类
/// </summary>
public abstract class Floor
{
/// <summary>
/// 创建地板
/// </summary>
public abstract void Create();
}

/// <summary>
/// 窗户抽象类,子类的窗户必须继承该类
/// </summary>
public abstract class Window
{
/// <summary>
/// 创建窗户
/// </summary>
public abstract void Create();
}

/// <summary>
/// 房门抽象类,子类的房门必须继承该类
/// </summary>
public abstract class Door
{
/// <summary>
/// 创建房门
/// </summary>
public abstract void Create();
}

/// <summary>
/// 欧式地板类
/// </summary>
public class EuropeanFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建欧式的地板");
}
}


/// <summary>
/// 欧式的房顶
/// </summary>
public class EuropeanRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建欧式的房顶");
}
}


/// <summary>
///欧式的窗户
/// </summary>
public class EuropeanWindow : Window
{
public override void Create()
{
Console.WriteLine("创建欧式的窗户");
}
}


/// <summary>
/// 欧式的房门
/// </summary>
public class EuropeanDoor : Door
{
public override void Create()
{
Console.WriteLine("创建欧式的房门");
}
}

/// <summary>
/// 现代的房顶
/// </summary>
public class ModernizationRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建现代的房顶");
}
}

/// <summary>
/// 现代的地板
/// </summary>
public class ModernizationFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建现代的地板");
}
}

/// <summary>
/// 现代的窗户
/// </summary>
public class ModernizationWindow : Window
{
public override void Create()
{
Console.WriteLine("创建现代的窗户");
}
}

/// <summary>
/// 现代的房门
/// </summary>
public class ModernizationDoor : Door
{
public override void Create()
{
Console.WriteLine("创建现代的房门");
}
}
2.6、 抽象工厂应对需求变更

让我们看看该模式如何应对需求的变化,假设我的表弟一看我们的房子很好,他也想要一套古典风格的房子(哈哈,这个家伙事挺多的,有好事总是落不下他)。

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
/// <summary>
///先为表弟的房子来建立一个工厂类吧
/// </summary>
public class ClassicalFactory : AbstractFactory
{
//创建房顶
public override Roof CreateRoof()
{
return new ClassicalRoof();
}

// 创建地板
public override Floor CreateFloor()
{
return new ClassicalFloor();
}

// 创建窗户
public override Window CreateWindow()
{
return new ClassicalWindow();
}

// 创建房门
public override Door CreateDoor()
{
return new ClassicalDoor();
}
}

/// <summary>
///古典的房顶
/// </summary>
public class ClassicalRoof : Roof
{
public override void Create()
{
Console.WriteLine("创建古典的房顶");
}
}

/// <summary>
/// 古典的地板
/// </summary>
public class ClassicalFloor : Floor
{
public override void Create()
{
Console.WriteLine("创建古典的地板");
}
}

/// <summary>
/// 古典的窗户
/// </summary>
public class ClassicalWindow : Window
{
public override void Create()
{
Console.WriteLine("创建古典的窗户");
}
}

/// <summary>
/// 古典的房门
/// </summary>
public class ClassicalDoor: Door
{
public override void Create()
{
Console.WriteLine("创建古典的房门");
}
}

此时,只需要添加五个类:一个是古典风格工厂类,负责创建古典风格的房子,另外几个类是具有古典风格的房顶、地板、窗户和房门的具体产品。从上面代码看出,抽象工厂对于系列产品的变化支持 “开放——封闭”原则(指的是要求系统对扩展开放,对修改封闭),扩展起来非常简便,但是,抽象工厂对于增加新产品这种情况就不支持”开放——封闭 “原则,因为要修改创建系列产品的抽象基类AbstractFactory,增加相应产品的创建方法,这也是抽象工厂的缺点所在。

三、抽象工厂的实现要点

  1. 如果没有应对“多系列对象创建”的需求变化,则没有必要使用AbstractFactory模式,这时候使用简单的静态工厂完全可以。
  2. “系列对象”指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中“道路”与“房屋”的依赖,“道路”与“地道”的依赖。
  3. AbstractFactory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
  4. AbstractFactory模式经常喝FactoryMethod模式共同组合来应对“对象创建”的需求变化。

抽象工厂模式的优点:

【抽象工厂】模式将系列产品的创建工作延迟到具体工厂的子类中,我们声明工厂类变量的时候是使用的抽象类型,同理,我们使用产品类型也是抽象类型,这样做就尽可能的可以减少客户端代码与具体产品类之间的依赖,从而降低了系统的耦合度。耦合度降低了,对于后期的维护和扩展就更有利,这也就是【抽象工厂】模式的优点所在。可能有人会说在Main方法里面(这里的代码就是客户端的使用方)还是会使用具体的工厂类,对的。这个其实我们通过Net的配置,把这部分移出去,最后把依赖关系放到配置文件中。如果有新的需求我们只需要修改配置文件,根本就不需要修改代码了,让客户代码更稳定。依赖关系肯定会存在,我们要做的就是降低依赖,想完全去除很难,也不现实。

抽象工厂模式的缺点:

有优点肯定就有缺点,因为每种模式都有他的使用范围,或者说要解决的问题,不能解决的问题就是缺点了,其实也不能叫缺点了。【抽象工厂】模式很难支持增加新产品的变化,这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

抽象工厂模式的使用场景:

如果系统需要多套的代码解决方案,并且每套的代码方案中又有很多相互关联的产品类型,并且在系统中我们可以相互替换的使用一套产品的时候可以使用该模式,客户端不需要依赖具体实现。

四、.NET中抽象工厂模式实现

微软的类库发展了这么多年,设计模式在里面有大量的应用,【抽象工厂】模式在.NET类库中也存在着大量的使用,比如和操作数据库有关的类型,这个类就是System.Data.Common.DbProviderFactory,这个类位于System.Data.dll程序集中。该类扮演抽象工厂模式中抽象工厂的角色,我们可以用ILSpy反编译工具查看该类的实现:

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
/// 扮演抽象工厂的角色
/// 创建连接数据库时所需要的对象集合,
/// 这个对象集合包括有 DbConnection对象(这个是抽象产品类,如绝味例子中的YaBo类)、DbCommand类、DbDataAdapter类,针对不同的具体工厂都需要实现该抽象类中方法,
public abstract class DbProviderFactory
{
public virtual bool CanCreateDataSourceEnumerator
{
get
{
return false;
}
}

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 CodeAccessPermission CreatePermission(PermissionState state)
{
return null;
}

public virtual DbDataSourceEnumerator CreateDataSourceEnumerator()
{
return null;
}
}

DbProviderFactory类是一个抽象工厂类,该类提供了创建数据库连接时所需要的对象集合的接口,实际创建的工作在其子类工厂中进行,微软使用的是SQL Server数据库,因此提供了连接SQL Server数据的具体工厂实现,具体代码可以用反编译工具查看,具体代码如下:

SqlClientFactory扮演着具体工厂的角色,用来创建连接SQL Server数据所需要的对象

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
public sealed class SqlClientFactory : DbProviderFactory, IServiceProvider
{
public static readonly SqlClientFactory Instance = new SqlClientFactory();

public override bool CanCreateDataSourceEnumerator
{
get
{
return true;
}
}

private SqlClientFactory()
{
}

public override DbCommand CreateCommand()
{
return new SqlCommand();
}

public override DbCommandBuilder CreateCommandBuilder()
{
return new SqlCommandBuilder();
}

public override DbConnection CreateConnection()
{
return new SqlConnection();
}

public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
return new SqlConnectionStringBuilder();
}

public override DbDataAdapter CreateDataAdapter()
{
return new SqlDataAdapter();
}

public override DbParameter CreateParameter()
{
return new SqlParameter();
}

public override CodeAccessPermission CreatePermission(PermissionState state)
{
return new SqlClientPermission(state);
}

public override DbDataSourceEnumerator CreateDataSourceEnumerator()
{
return SqlDataSourceEnumerator.Instance;
}

object IServiceProvider.GetService(Type serviceType)
{
object result = null;
if (serviceType == GreenMethods.SystemDataCommonDbProviderServices_Type)
{
result = GreenMethods.SystemDataSqlClientSqlProviderServices_Instance();
}
return result;
}
}

OdbcFactory也是具体工厂类

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
public sealed class OdbcFactory : DbProviderFactory
{
public static readonly OdbcFactory Instance = new OdbcFactory();

private OdbcFactory()
{
}

public override DbCommand CreateCommand()
{
return new OdbcCommand();
}

public override DbCommandBuilder CreateCommandBuilder()
{
return new OdbcCommandBuilder();
}

public override DbConnection CreateConnection()
{
return new OdbcConnection();
}

public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
return new OdbcConnectionStringBuilder();
}

public override DbDataAdapter CreateDataAdapter()
{
return new OdbcDataAdapter();
}

public override DbParameter CreateParameter()
{
return new OdbcParameter();
}

public override CodeAccessPermission CreatePermission(PermissionState state)
{
return new OdbcPermission(state);
}
}

当然,我们也有OleDbFactory 类型,都是负责具体的数据库操作。DbProviderFactory就是【抽象工厂】模式UML里面AbstractFactory类型。其他具体的工厂类型继承DbProviderFactory类型,这个结构很简单,我就不画图了。

五、总结

终于写完了,写了3个小时,学习设计模式不能死学,要把握核心点和使用场景。关键点第一是,面向对象设计模式的基本原则,有了原则,考虑问题就不会跑偏,然后再仔细把握每种模式的使用场景和要解决的问题,多写写代码,多看看Net的类库,它是最好的教材。

本文链接: https://netlover.cn/2018/08/18/csharp-abstract-factory-pattern.html