1.工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。【DP】
下图是抽象工厂模式结构图:
AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象是因为它们都有可能有两种不同的实现。而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现。
系统在运行时创建一个ConcreteFactroy类的实例,这个具体的工厂再创建具有特定实现的产品对象。若要创建不同的产品对象,客户端应该使用不同的具体工厂。
2.下面是将抽象工厂模式在数据库方面的应用例子。
这个抽象工厂实现了对数据库表User和Department的访问,并且支持SqlServer和Access两种数据库。User和Department相当于1中的AbstractProductA何AbstractProductB,而SqlserverUser便是ProductA1,SqlserverDepartment便是ProductB1。
当数据库要从SqlServer改为Access时,只需对下列代码进行更改。
1 User user = new User();
2 Department dept = new Department();
3
4 // IFactory factory = new SqlserverFactory(); // 从SqlServer
5 IFactory factory = new AccessFactory(); // 转为使用Access
6
7 IUser iu = factory.createUser(); // 已经与具体的数据库访问解除依赖关系(不管是SqlServer还是Access)
8 iu.insert(user);
9 iu.getUser(1);
10
11 IDepartment iDept = factory.createDepartment(); // 已经与具体的数据库访问解除依赖关系(不管是SqlServer还是Access)
12 iDept.insert(dept);
13 iDept.getDepartment(1);
3.抽象工厂的优点:
- 便于交换产品系统,有了具体工厂类的存在,在一个应用中只需在初始化一次,就可以改变一个应用的具体工厂,改变了具体工厂即可使用不同的产品配置。
- 具体的创建实例过程与客户端分离,客户端是通过它们的对象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端中。
上例中,客户端所认识的只有IUser和IDepartment,并不知是用SqlServer还是Access来实现。
4.对2中的例子进行优化
在例子中,若此时需要增加一个数据库表Project,则至少要增加三个类:IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory、AccessFactory才能完全实现!
其实,我们可以使用简单工厂对抽象工厂进行优化:
在类DataBaseAccess中这样实现:
1 //std::string DataBaseAccess::db = "Sqlserver";
2 std::string DataBaseAccess::db = "Access";
3 class DataBaseAccess
4 {
5 public:
6 static IUser *createUser()
7 {
8 IUser *pUser = NULL; // 其实IUser * 应该使用static IUser*作为类的成员变量比较合理,
9 switch(db) // 这里为了节省时间忽略
10 {
11 case "Sqlserver":
12 pUser = new SqlserverUser();
13 break;
14 case "Access":
15 pUser = new AccessUser();
16 break;
17 }
18 return pUser;
19 }
20 // createDepartment 的实现方法类似
21 private:
22 static std::string db;
23 };
经过这样处理,在客户端中完全可以完全不出现SqlServer和Access等与数据库相关的字样,只需要更改字符串db的值便可以访问不同的数据库,达到解耦目的。
5.再进一步优化(依赖注入 Dependency Injection)
(注意:本节所讲的反射为JAVA中的技术,C++中是否能使用上本人尚未尝试,所举例子只为讲明方法的伪代码。在C++有个AGM的库用宏实现了类似JAVA的反射技术,有时间了解一下)
可使用反射技术来解决程序中对db的switch判断。
格式:
using System.Reflection;
Assembly.Load("程序集名称").CreateInstance("命名空间.类名称")
现在使用反射技术队DataBaseAccess改进:
1 //std::string DataBaseAccess::db = "Sqlserver";
2 std::string DataBaseAccess::db = "Access";
3 std::string DataBaseAccess::AssemblyName="抽象工厂模式";
4 class DataBaseAccess
5 {
6 public:
7 static IUser *createUser()
8 {
9 //IUser *pUser = NULL; // 其实IUser * 应该使用static IUser*作为类的成员变量比较合理,
10 //switch(db) // 这里为了节省时间忽略
11 //{
12 //case "Sqlserver":
13 // pUser = new SqlserverUser();
14 // break;
15 //case "Access":
16 // pUser = new AccessUser();
17 // break;
18 //}
19 //return pUser;
20 std::string className = AssemblyName + "." + db + "User";
21 return (IUser *) Assembly.Load(AssemblyName).CreateInstance(className);
22 }
23 // createDepartment 的实现方法类似
24 private:
25 static std::string db;
26 static std::string AssemblyName;
27 };
现在在程序中可以完全消除了判断,甚至,我们可以将db的值保存在外部的配置文件里面,程序运行后在赋给db,这样做还能从程序外部来实现不同数据库的访问!