前言
在设计模式有三个模式是与工厂模式相关的,分别是:简单工厂模式、工厂方法模式以及抽象工厂模式。在前面的文章中已经谈到前面两种,这里就对抽象工厂模式介绍一下。抽象工厂模式就是提供一个创建一系列相关或者相互依赖的接口(也就是抽象类),而无需指定具体的类。简单来说,就是当我们需要创建一个具体的对象的时候,我们不必指定该具体的对象,只需要使用它的上层接口直接调用就行。好像还是很抽象哦,好吧,为了更清晰领悟这个设计模式,我们还是通过一个案例来说明
问题背景
某公司开发了一个A软件,数据库使用的是SQLServer。后由于客户要求需要使用Oracle数据库,原来的数据要迁移到Oracle中,在迁移的过程中遇到很多问题,比如语法错误,关键字滥用,函数不支持等问题。请设计一组程序,实现数据的无缝迁移。
简介
通过使用我们的工厂方法模式,我们已经较好的解决了用户使用不同数据库的问题,现在我们已经获得了要创建的数据库对象,那么接下来我们就需要向数据库中添加数据了,假设都需要添加部门信息,有的用户需要使用Oracle,有的用户需要使用SqlServer。考虑抽象工厂模式的代码结构:
抽象工厂模式实现
在修改代码结构之前,为了更好的对比,来看一下我们现在的代码结构:
修改之前(基于抽象工厂模式)
原始代码v1.0:
public interface IFactory {
//创建数据库对象
DBObject createDBObject();
//创建部门
IDepartment createDepartment();
}
public class OracleFactory implements IFactory{
@Override
public DBObject createDBObject() {
return new OracleObject();
}
@Override
public IDepartment createDepartment() {
return new OracleDepartment();
}
}
public class SqlServerFactory implements IFactory{
@Override
public DBObject createDBObject() {
return new SqlServerObejct();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
public abstract class DBObject {
//添加用户的方法
public abstract void insertUser(User user);
//查找用户信息的方法
public abstract User findUserById(int id);
}
public class OracleObject extends DBObject{
@Override
public void insertUser(User user) {
System.out.println("使用Oracle添加用户");
}
@Override
public User findUserById(int id) {
System.out.println("使用Oracle通过id找到用户");
return null;
}
}
public class SqlServerObejct extends DBObject{
@Override
public void insertUser(User user) {
System.out.println("使用SQLServer添加用户");
}
@Override
public User findUserById(int id) {
System.out.println("使用SQLServer通过id找到用户");
return null;
}
}
public interface IDepartment {
//创建部门
void insertDept(Department dept);
//查找部门
Department findDeptById(int id);
}
public class OracleDepartment implements IDepartment {
@Override
public void insertDept(Department dept) {
System.out.println("使用Oracle对象插入一条部门信息");
}
@Override
public Department findDeptById(int id) {
System.out.println("使用Oracle对象查找id为" + id + "的部门信息");
return null;
}
}
public class SqlServerDepartment implements IDepartment {
@Override
public void insertDept(Department dept) {
System.out.println("使用SqlServer对象添加一条部门信息");
}
@Override
public Department findDeptById(int id) {
System.out.println("使用SqlServer对象查找id为" + id + "的部门信息");
return null;
}
}
//测试方法
public static void main(String[] args){
IFactory factory = new OracleFactory();
IFactory factory2 = new SqlServerFactory();
IDepartment department = factory.createDepartment();
IDepartment department2 = factory2.createDepartment();
department.insertDept(dept);
department.findDeptById(1);
department2.insertDept(dept);
department2.findDeptById(2);
}
第一次修改(基于简单工厂模式)
根据抽象工厂模式结构图以及需求,修改代码如下:
1) 增加一个创建部门的抽象工厂,这里使用接口
2) 分别创建基于Oracle以及基于SqlServer的部门实现类(已经在上面的代码中实现)
3) 创建一个数据库工具类,根据条件创建需要的对象
修改的代码v1.1
public class DBAccess {
//这里固定DBTYPE的值,如果要使用其他数据库,修改这个字段的值就好
private static final String DBTYPE = "oracle";
public static DBObject createDBObject(){
switch (DBTYPE){
case "oracle":
return new OracleObject();
case "sqlserver":
return new SqlServerObejct();
}
return null;
}
public static IDepartment createDepartment(){
switch (DBTYPE) {
case "oracle":
return new OracleDepartment();
case "sqlserver":
return new SqlServerDepartment();
}
return null;
}
}
//测试方法
public static void main (String[] args){
User user = new User();
Department dept = new Department();
//这里创建出来的DBObject就是上面DBTYPE指定值的数据库对象
DBObject dbObject = DBAccess.createDBObject();
dbObject.insertUser(user);
dbObject.findUserById(1);
IDepartment department = DBAccess.createDepartment();
department.insertDept(dept);
department.findDeptById(1);
}
这里通过DBAccess类直接替代了原来的IFactory、OracleFactory、SqlServerFactory三个类,这里实际上是简单工厂的实现方法,把判断逻辑转移到DBAccess中,客户端不需要做任何修改,要更换数据库只需要在DBAccess中把DBTYPE的值修改就ok。在这点上,好像简单工厂占优势,但是不尽然,如果要在程序中添加对MySQL数据库的支持,如果使用原来抽象工厂模式。只需要添加一个MySQLFactory类就行,现在就需要在DBAccess类中修改逻辑判断了(在这一点违背了封闭开放原则)。那么有没有一种方法,不要增加对switch的判断就可以根据DBTYPE的值创建相应的对象呢?也就是说,在程序运行期间,根据类的字符串表示创建类的实例。答案是肯定的,在Java中我们可以利用反射技术做到这一点,所以我们对v1.1版的代码进行进一步的修改:
第二次修改(基于反射技术)
v1.2
public class DBAccess {
private static final String DBTYPE = "Oracle";
public static DBObject createDBObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
return (DBObject) Class.forName("com.rhwayfun.GoF." + DBTYPE + "Object").newInstance();
}
public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException{
return (IDepartment) Class.forName("com.rhwayfun.GoF." + DBTYPE + "Department").newInstance();
}
}
现在,如果需要增加对MySQL的支持只需要修改Oracle为MySQL就行,不过还是得创建MySQLObject类以及MySQLDepartment类,但是与上面简单工厂模式的修改不同,这里只是在原来代码的基础增加,是扩展而不是修改。所以没有违背开放-封闭原则,现在这版的代码比之前好多了,但是DBTYPE的值仍然不可避免要进行修改,事实上还有一种通过配置文件的方式可以保存原来的代码不变而实现这个功能,下面对代码进行第三次修改:
第三次修改(基于配置文件+反射技术)
首先创建db.properties配置文件
DBTYPE=Oracle
v1.3版代码
public class DBAccess {
public static DBObject createDBObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException{
return (DBObject) Class.forName("com.rhwayfun.GoF." + readPro() + "Object").newInstance();
}
public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException{
return (IDepartment) Class.forName("com.rhwayfun.GoF." + readPro() + "Department").newInstance();
}
private static String readPro() throws IOException{
Properties pro = new Properties();
InputStream in = DBAccess.class.getClassLoader().getResourceAsStream("com/rhwayfun/GoF/db.properties");
pro.load(in);
return (String) pro.get("DBTYPE");
}
}
客户端代码保持不变,实际测试成功。
抽象工厂模式小结
从v1.0到v1.3我们对工厂模式做一个简单的小结:
- 所有使用简单工厂的都可以考虑使用反射技术加以优化
- 抽象工厂的最大好处是便于交换产品系列,要创建不同的产品,我们可以使用不同的工厂创建。所以当我们需要不同产品线的产品的时候只需要换一个工厂就可以轻松实现
- 抽象工厂模式与工厂方法模式把创建实例的过程与客户端分离,用户操作的只是抽象接口,而具体的创建过程则封装到了具体的工厂去实现。很好的体现了开放-封闭的原则
- 抽象工厂模式与抽象工厂模式都存在一个缺点,就是需要增加新功能的时候,要创建的类很多。
- 简单工厂模式可以通过反射技术克服上诉缺点,但使用反射会降低程序的性能,所以虽然使用反避免了创建多个类的麻烦,却也同时降低了程序的性能,所谓有得必有失。在实际的开发中还是需要仔细衡量的。