第六课 抽象工厂模式
抽象工厂模式其实和工厂方法模式思路一样,可以说是工厂方法模式的一个特例,用于控制一个系列的产品的不同版本。简单举个例子(可能不适用于.net)我现在有几个界面UI控件产品,Button,Label,TextBox。大家应该知道,windows和Mac系统底层对UI的实现不同,所以创建控件的方式可能不同。这样我可能出2套产品,一套是为Windows实现的,另一套是基于Mac实现的。这样我为了方便的获得两套产品,就需要用抽象工厂模式。换句话说,抽象工厂在应用的时候,需要判断产品的横向和纵向。假设横向是一个系列的产品,纵向是不同的版本实现的话,就有类似下面这样的一个矩阵。
ButtonForWin LabelForWin TextBoxForWin
ButtonForMac LabelForMac TextBoxForMac
ButtonForLinux LabelForLinux TextBoxForLinux
其实就是3个产品,Button Label TextBox ,但是针对3个操作系统实现了3个版本而已。
这样大家有点印象了吧,下面开始看代码。Java版
首先系列产品的抽象
1. public interface Button
2. {
3. ...
4. }
5.
6. public interface Label
7. {
8. ...
9. }
这里我懒的打TextBox了,就用俩来看吧。
然后Windows实现:
1. public class WinButton implements Button
2. {
3. ...
4. }
5.
6. public class WinLabel implements Label
7. {
8. ...
9. }
再看Mac实现:
1. public class MacButton implements Button
2. {
3. ...
4. }
5.
6. public class MacLabel implements Label
7. {
8. ...
9. }
Windows和Mac的实现UI方式肯定不同,这里不描述了。
然后来看工厂:
1.
2. public abstract class UIProducer
3. {
4. public abstract Button createButton();
5.
6. public abstract Label createLabel();
7.
8. public static UIProducer getInstance(String which)
9. {
10. if (which.equalsIgnoreCase("Win"))
11. {
12. return WinProducer.getInstance();
13. }
14. else if (which.equalsIgnoreCase("Mac"))
15. {
16. return MacProducer.getInstance();
17. }
18. return null;
19. }
20. }
21.
22. public class WinProducer extends UIProducer
23. {
24. private static WinProducer producer = new WinProducer ();
25.
26. private WinProducer()
27. {
28. }
29.
30. public Button createButton()
31. {
32. return new WinButton();
33. }
34.
35. public Label createLabel()
36. {
37. return new WinLabel();
38. }
39.
40. public static WinProducer getInstance()
41. {
42. return producer;
43. }
44.
45.
46. }
47.
48. public class MacProducer extends ComputerProducer
49. {
50. private static MacProducer producer = new MacProducer();
51.
52. private MacProducer() {
53. }
54.
55. public Button createButton()
56. {
57. return new MacButton();
58. }
59.
60. public Label createRam()
61. {
62. return new MacLabel();
63. }
64.
65. public static MacProducer getInstance()
66. {
67. return producer;
68. }
69.
70. }
这里使用了类似工厂方法模式,使用了一个抽象工厂基类。但是实现方式不同,请仔细观察。这里没有用接口,使用了抽象类,同事没有定义抽象生产方法,采用了简单工厂模式的方式返回实现的工厂对象。这里如果我系统是针对Windows开发的,那么我可以使用:
1. UIProducer winUI = UIProducer.getInstance("Win");
来获得生产Windows下产品的工厂,反之使用:
2. UIProducer winUI = UIProducer.getInstance("Mac");
来获得生产Mac系统下产品的工厂。
现在大家能够体会到抽象工厂的用法了吧。
在你判断一套产品有可能要为不同的需求做几套不同的实现的产品的时候,其实简单说就是一套东西要做几个版本的实现的时候,就可以考虑抽象工厂。
再来看一个《大话设计模式》中的例子,这个是针对不同种类数据库实现的例子。
C#代码:
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4. using System.Reflection;
5. using System.Configuration;
6.
7. namespace 抽象工厂模式
8. {
9. class Program
10. {
11. static void Main (string[] args)
12. {
13. User user = new User();
14. Department dept = new Department();
15.
16. IUser iu = DataAccess.CreateUser();
17.
18. iu.Insert(user);
19. iu.GetUser(1);
20.
21. IDepartment id = DataAccess.CreateDepartment();
22. id.Insert(dept);
23. id.GetDepartment(1);
24.
25. Console.Read();
26. }
27. }
28.
29. class User
30. {
31. private int _id;
32. public int ID
33. {
34. get { return _id; }
35. set { _id = value; }
36. }
37.
38. private string _name;
39. public string Name
40. {
41. get { return _name; }
42. set { _name = value; }
43. }
44. }
45.
46. class Department
47. {
48. private int _id;
49. public int ID
50. {
51. get { return _id; }
52. set { _id = value; }
53. }
54.
55. private string _deptName;
56. public string DeptName
57. {
58. get { return _deptName; }
59. set { _deptName = value; }
60. }
61. }
62.
63. interface IUser
64. {
65. void Insert(User user);
66.
67. User GetUser(int id);
68. }
69.
70. class SqlserverUser : IUser
71. {
72. public void Insert(User user)
73. {
74. Console.WriteLine("在Sqlserver中给User表增加一条记录");
75. }
76.
77. public User GetUser(int id)
78. {
79. Console.WriteLine("在Sqlserver中根据ID得到User表一条记录");
80. return null;
81. }
82. }
83.
84. class AccessUser : IUser
85. {
86. public void Insert(User user)
87. {
88. Console.WriteLine("在Access中给User表增加一条记录");
89. }
90.
91. public User GetUser(int id)
92. {
93. Console.WriteLine("在Access中根据ID得到User表一条记录");
94. return null;
95. }
96. }
97.
98. interface IDepartment
99. {
100. void Insert(Department department);
101.
102. Department GetDepartment(int id);
103. }
104.
105. class SqlserverDepartment : IDepartment
106. {
107. public void Insert(Department department)
108. {
109. Console.WriteLine("在Sqlserver中给Department表增加一条记录");
110. }
111.
112. public Department GetDepartment(int id)
113. {
114. Console.WriteLine("在Sqlserver中根据ID得到Department表一条记录");
115. return null;
116. }
117. }
118.
119. class AccessDepartment : IDepartment
120. {
121. public void Insert(Department department)
122. {
123. Console.WriteLine("在Access中给Department表增加一条记录");
124. }
125.
126. public Department GetDepartment(int id)
127. {
128. Console.WriteLine("在Access中根据ID得到Department表一条记录");
129. return null;
130. }
131. }
132.
133. class DataAccess
134. {
135. private static readonly string AssemblyName = "抽象工厂模式";
136. private static readonly string db = ConfigurationManager.AppSettings["DB"];
137.
138. public static IUser CreateUser()
139. {
140. string className = AssemblyName + "." + db + "User";
141. return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
142. }
143.
144. public static IDepartment CreateDepartment()
145. {
146. string className = AssemblyName + "." + db + "Department";
147. return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
148. }
149. }
150.
151. }
这里一共出现了2个产品对象,用户和部门。
针对两个产品做了2个版本实现 Access 和 SqlServer 。
这样的好处是什么呢,如果我现在要把系统安装在一个装有SqlServer数据库的机器上的时候,我就可以直接调用SqlServer工厂类,给我生产Sql版的产品来用,如果装在一个只有Access数据库的机器上时,我可以直接用Access工厂类,生产Access产品来使用。不用修改代码。你可能会问,为什么不用修改代码,调用的时候需要给参数啊。这里你看看
” DataAccess” 这个类的实现方法。这里应用了一个动态加载类的方式 :
Assembly.Load(AssemblyName).CreateInstance(className);
这个方式可以通过一串文本(指明类的目录及类名)来生成类对象。当然生成的是Object对象,但是可以通过强制转换成基类来调用吗。你又要问了,字符串也是后台写得啊,还是需要改代码啊。我回答,笨啊你,谁说字符串非要写在后台,我写到配置文件里去不行吗?你看:
private static readonly string db = ConfigurationManager.AppSettings["DB"];
这句是干什么用的啊。这不是读配置文件里的节点吗。
下面给出配置文件:
1. <?xml version="1.0" encoding="utf-8" ?>
2. <configuration>
3. <appSettings>
4. <add key="DB" value="Sqlserver"/>
5. </appSettings>
6. </configuration>
看到了没这里配置的是Sqlserver 所以后台肯定是生成SqlServer工厂了。
这样我的程序如果改用Access数据库的话,我只要修改下配置文件即可。完全避免的代码的修改。怎么样,有用吧。
好了,今天的课就到这里,明天讲什么呢,我回去想想吧。同志们期待吧。
作者:王文斌