15.3 用工厂方法模式的数据访问程序
现在想用不同的数据库连接,获得数据,但是每个数据库连接的方法可能有些不同。
想用工厂方法兼容不同的数据库。
现在想在User表获得记录和添加记录。SqlServer和Access都有user表。
User接口,用户客户端访问,解除和具体数据库的耦合
public interface IUser {
void insert(User user);
User getUser(int id);
}
SqlserverUser类,用于访问SQL server的user
public class SqlserverUser implements IUser {
void insert(User user){
println("在SQL server中给user加条记录");
}
User getUser(int id){
println("根据ID获得USER表中一条记录");
return null;
}
}
AccessUser类也是类似的。
IFactory接口,定义一个创建访问User表对象的抽象工厂接口
public interface IFactory {
IUser createUser();
}
SqlServerFactory类,实现IFactory接口,实例化SqlserverUser
public class SqlServerFactory implements IFactory {
public IUser createUser(){
return new SqlserverUser();
}
}
AccessFactory类也是类似的。
客户端代码
public class Main {
public static void main(String[] args){
User user = new User();
//如果是Access数据库,则改成new AccessFactory();
IFactory factory = new SqlServerFactory();
IUser iu = factory.createUser();
iu.insert(user);
iu.getUser(1);
}
}
问题
但是现在数据库里不可能只有一个User表,很可能有其他表,比如部门表。那就需要增加好多类了。
15.4 用抽象工厂模式的数据访问程序
//接口
public interface IDepartment {
void insert(Department department);
User getUser(int id);
}
//Sqlserver的实现
class SqlserverDepartment implements IDepartment {
public void insert(Department department){
println("在SQL server中给department加条记录");
}
public User getUser(int id){
println("根据ID获得department表中一条记录");
return null;
}
}
//IFactory接口中增加一个接口方法
public interface IFactory {
IUser createUser();
IDepartment createDepartment();
}
//SqlServerFactory类,实例化createDepartment方法
public class SqlServerFactory implements IFactory {
public IUser createUser(){
return new SqlserverUser();
}
public IDepartment createDepartment(){
return new SqlserverDepartment();
}
}
只有一个Uuser类和User操作类的时候,是只需要工厂方法模式的,但是现在数据库有很多表而数据库也有不同的,所以解决这种涉及多个产品系列的问题,就用抽象工厂模式。
15.5 抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而不需指定它们具体的类。
AbstractProductA和AbstractProductB是两个抽象产品,因为下面可能有多种不同的实现,就像刚才的User和Department,而不同的ProductA1,ProductA2等等,就是抽象产品的具体分类实现。例如SqlserverUser理解为ProductA1,AccessUser是ProductB1。
IFactory是一个抽象工厂接口,里面应包含所有的产品创建的抽象方法。
ConcreteFactory1和ConcreteFactory2就是具体工厂,就像SqlserverFactory和AccessFactory一样。
运行时再创建一个ConcreteFactory类的实例,为创建不同的产品对象,客户端应使用不同的具体工厂。
15.6 抽象工厂模式的优点和缺点
优点:
- 易于交换产品系列,具体工厂类,例如IFactory factory = new AccessFactory(),在一个应用中只需在初始化出现一次,使得改变一个应用的具体工厂变得很容易。
- 让具体的创建实例过程和客户端分离,客户端只是通过它们的抽象接口操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码。
缺点:
- 如果增加一个产品的话,例如又增加项目表Project。要增加很多类,AbstractProduct和ConcreteProduct。还要修改类Factory,ConcreteFactory。很麻烦
- 在客户端如果我有100个ConcreteFactory,可能就要调用100次。
15.7 用简单工厂改进抽象工厂
去除IFactory,SqlserverFactory和AccessFactory三个工厂,用DataAccess类取代,用简单工厂模式来实现。
public class DataAccess {
//数据库名称,可替换成Access
private static final String db = "Sqlserver";
public static IUser createUser(){
IUser result = null;
switch(db){
case "Sqlserver":
result = new SqlserverUser();
break;
case "Access":
result = new AccessUser();
break;
default:break;
}
return result;
}
public static IDepartment createDepartment(){
IDepartment result = null;
switch(db){
case "Sqlserver":
result = new SqlserverDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
default:break;
}
return result;
}
}
客户端代码
public class Main {
public static void main(String[] args){
User user = new User();
Department dept = new Department();
//直接得到实际数据库访问实例,不存任何依赖
IUser iu = DataAccess.createUser();
iu.insert(user);
iu.getUser(1);
//...同Department
}
}
问题:
如果增加一个ConcreteFactory,本来抽象工厂增加一个具体工厂类只需要实现一下就好了,现在就需要在DataAccess类中每个方法的switch加case
15.8 用反射+抽象工厂的数据访问程序
将DataAccess中使用switch的部分,都替换成通过反射获取。
public class DataAccess {
//数据库名称,可替换成Access
private static final String db = "Sqlserver";
public static IUser createUser(){
//SqlserverUser
String className = "com.wt.design.product."+ db + "User";
Class iuserClass = Class.forName(className);
return (IUser)iuserClass.newInstance();
}
public static IDepartment createDepartment() {
//...
}
}
如果有新的表Project呢?增加Project相关表,再修改DataAccess,在其中增加一个public static IProject createProject()方法就可以了。
DataAccess中的db变量(表示用哪个数据库的),可以通过设置properties属性来改进成可动态修改的即可。