抽象工厂模式是对工厂模式的一种改进。它提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
如何去理解这句话呢?
假设我们需要编写一个数据库访问程序,数据库会有很多表,那么对于每一张表的访问,我们可以理解为一种具体的对象。我们的数据库肯定不止一种,那么对于每一种数据库,我们都应该有相应的操作程序。这就是具体的需求了。
那么抽象工厂会如何去处理呢?首先,我们需要有针对于每张表的接口,接口抽象出了对表数据进行的具体操作。
public interface IUser {
void insertUser();
void selectUser();
}
public interface IDepartment {
void insertDepartment();
void selectDepartment();
}
然后,对于每个接口,分别会有对应的数据库访问类去实现这个接口
public class SqlServerUser implements IUser {
@Override
void insertUser(){
sout("sqlserver插入用户");
}
@Override
void selectUser() {
sout("sqlserver查询用户");
}
}
public class MysqlUser implements IUser{
@Override
void insertUser(){
sout("mysql插入用户");
}
@Override
void selectUser(){
sout("mysql查询用户");
}
}
…部门操作省略
有了上述的类,我们就可以开始编写工厂了。
首先需要一个抽象工厂,这个工厂拥有生产所有产品的能力
public abstract class AbstractSqlFactory {
public abstract IUser createUser();
public abstract IDepartment createDepartment();
}
可以看到,这里有两个方法,分别用来生产用户表操作类和组织表操作类。
具体的工厂如下
public class SqlServerFactory extends AbstractSqlFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
…mysql工厂省略
客户端调用如下
IFactory factory = new SqlServerFactory();
IUser user = factory.createUser();
user.insertUser();
user.selectUser();
利用抽象工厂,我们可以得到的好处是,易于交换产品系列。具体的工厂类在一个应用中只需要在初始化的时候出现一次,这样使得改变一个工厂变的非常容易。第二个好处是,它让创建具体的实例与客户端分离,客户端通过抽象接口操控实例。比如刚才的例子,客户端实际上只认识IUser和IDepartment,不会认识具体的类。
但抽象工厂也有不好的地方,虽然它在切换产品的时候很好用,但是如果我们想要增加一个表的操作,比如Project表,那么我们需要增加IProject、SqlServerProject、MysqlProject这三个类,还需要修改AbstractSqlFactory、SqlServerFactory、MysqlFactory这三个类,这是很糟糕的。增加一个操作,需要做这么大的改动。所以我们要对最基本的抽象工厂模式进行优化。
首先我们很容易想到的是,利用简单工厂模式来进行优化。取消工厂的抽象,换用一个通用的DataAccess类来进行处理
public class DataAccess {
private static final String db = "SqlServer";
public static IUser createUser() {
switch(db) {
case "SqlServer":
return new SqlServerUser();
case "Mysql":
return new MysqlUser();
default:
return null;
}
}
public static IDepartment createDepartment() {
……
}
}
这样客户端在调用的时候,只需要调用createUser或者createDepartment方法即可,解除了和具体工厂的耦合。要修改数据库实现的时候,也只需要修改db的值即可。但如果我们现在要增加一个对Oracle数据库的访问,事情就会变的麻烦起来。我们还需要修改每个方法中的switch语句,加入对Oracle数据库的判断。
那么有没有什么方法可以改进这种switch选择呢?其实我们可以利用反射来动态加载具体的类。这样这个类的路径可以写在一个变量中,或者干脆放在配置文件中,这样就解决了switch的问题。
相关demo可以参考我的gitee仓库
https://gitee.com/akitsuki-kouzou/DesignPatternDemo