什么是抽象工厂模式?
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。[DP]
前两节分别学习了简单工厂模式与工厂方法模式,其中,工厂方法模式是为了解决简单工厂模式的扩展问题而出现的,但随之而来的就是其只能够“生产”同一类产品(产品族),如果要增加新的产品族,就比较麻烦。
抽象工厂模式就是为解决这个问题而出现的,来看看它的UML类图:
该模式是工厂方法模式的升级版本,主要是为了解决多产品族的问题。当需要增加新的业务时,增加新的产品类即可。
实现
为了能够更好的区分工厂方法模式和抽象工厂模式,我们用两种方式来实现数据库访问。要求如下:“由于不同的客户要求,网站使用的数据库是不一样的,如何设计系统使得可以维护使用Access和使用SQL Server的数据库。”
工厂方法模式
假设现在系统只需要操作User表,两种数据库:Sql Server与Access。其UML类图如下:
User类
User类只有id和name
public class User {
private int _id;
private String name;
public int getId() {
return _id;
}
public void setId(int id) {
this._id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
IUser接口
因为不同数据库的SQL语句是不一样的,因此该接口是为了解耦客户端与底层数据库。为简易代码,以插入/获取User为例:
public interface IUser {
public void insert(User user);
public User getUser(int id);
}
SqlserverUser类
使用Sqlserver数据库时操作User:
public class SqlserverUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在SQL Sever中给User表增加记录");
}
@Override
public User getUser(int id) {
System.out.println("在SQL Sever中根据id获取User表的记录");
return null;
}
}
AccessUser类
使用Access数据库时操作User:
public class AccessUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在Access中给User表增加记录");
}
@Override
public User getUser(int id) {
System.out.println("在Access中根据id获取User表的记录");
return null;
}
}
工厂接口
所有数据库都得实现该工厂接口:
//工厂方法
public interface IFactory {
IUser createUser();
}
SqlserverFactory类
SqlserverFactory实现工厂接口,用于使用Sqlserver进行访问:
public class SqlserverFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlserverUser();
}
}
AccessFactory省略。
Client1
当需要更换数据库时,修改一条语句即可:
//工厂方法实现的客户端
public class Client1 {
public static void main(String[] args) {
User user = new User();
//换成AccessFactory()即可更换数据库
IFactory factory = new SqlserverFactory();
IUser iUser = factory.createUser();
iUser.insert(user);
iUser.getUser(1);
}
}
结果如下:
从上面可以看出,此时更换数据库直接替换一条语句即可,从而实现了业务与数据的解耦。但是如果除了User表,还有Department表呢?或者更多的呢?
抽象工厂模式
当有多个表、多个产品类时,普通的工厂方法模式就变成了抽象工厂模式。
添加Department类
public class Department {
private int _id;
private String departmentName;
public int getId() {
return _id;
}
public void setId(int id) {
this._id = id;
}
public String getName() {
return departmentName;
}
public void setName(String name) {
this.departmentName = name;
}
}
IDepartment类、AccessDepartment类等省略,详见源码。
修改IFactory
需要修改IFactory类,添加department相关的代码
public interface IFactory {
IUser createUser();
IDepartment createDepartment();
}
Client2
import com.whitedew.abstractfactory.impl.AccessFactory;
//抽象工厂模式
public class Client2 {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
//SqlserverFactory();
IFactory factory = new AccessFactory();
IUser iUser = factory.createUser();
iUser.insert(user);
iUser.getUser(1);
IDepartment iDepartment = factory.createDepartment();
iDepartment.insert(department);
iDepartment.getDepartment(1);
}
}
结果如下:
只有一个User类和User操作类的时候,是只需要工厂方法模式的,但如果有多个类并且又多个数据库操作时,工厂方法就不适用的。所以解决这种涉及到多个产品系列的问题,就需要使用抽象工厂方法。
抽象工厂+简单工厂
仔细分析上述代码,如果还需要增加一个业务表呢?比如Price表,需要做些什么?
增加三个类:IPrice、SqlserverPrice、AccessPrice;
修改三个地方:IFactory、SqlserverFactory、AccessFactory;
而且如果使用的数据库种类越多,需要增加和修改的地方就更多。
在这种情况下,可以使用简单工厂模式,将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;
}
return result;
}
public static IDepartment CreateDepartment() {
IDepartment result = null;
switch (db) {
case "Sqlserver":
result = new SqlserverDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
}
return result;
}
}
这时只需要在客户端调用DataAccess类即可:
//抽象工厂加简单工厂
public class Client3 {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
IUser iUser = DataAccess.CreateUser();
iUser.insert(user);
iUser.getUser(1);
IDepartment iDepartment = DataAccess.CreateDepartment();
iDepartment.insert(department);
iDepartment.getDepartment(1);
}
}
使用抽象工厂+简单工厂,可以很大的简化代码,并且将修改和增加的地方都收敛至一处,一定程度上降低了开发的难度。
反射机制
在DataAccess中,如果需要更换数据库,需要手动修改,并且其中还有switch-case之类的语句:
通过反射机制可以解决这个问题。有关反射机制,简单来说,就是在程序运行的过程中,根据类属性,去动态地创建对应的类实例,而不需要像上面这样通过硬编码的方式来选择数据库。详情请见:链接
Java中反射可以这么实现,参考简说设计模式:
public class DataAccess {
private static final String name = "com.whitedew.abstractfactory.impl";
private static final String db = "Access";//要更换,换成Sqlserver即可
public static IUser createUser() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String className = name + "." + db + "User";
return (IUser) Class.forName(className).newInstance();
}
public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String className = name + "." + db + "Department";
return (IDepartment) Class.forName(className).newInstance();
}
}
总结
现在我们来对三种工厂模式做一个总结:
- 简单工厂模式:通过接收的参数不同,来返回不同的对象实例,实现了客户端与具体业务对象的解耦;
- 工厂方法模式:在简单工厂模式的基础上进一步完善,符合开放-封闭原则;
- 抽象工厂模式:抽象工厂是应对多层业务耦合所出现的,典型的例子就是上述的数据与业务对象;
这三种模式在实际开发中非常实用,有关三者的取舍需要根据实际情况来确定,甚至有的时候需要进行重构。