将关联零件组装成产品
1.基本介绍
抽象工厂是工厂方法的扩展,工厂方法一般只能生成一种实例,而抽象工厂可以生成多种实例.
2.代码实现
Factory:抽象工厂接口
/**
* @author Jay
* @date 2019/7/4 22:26
* @description 抽象方法
* 抽象工厂可以生产多个抽象产品
*/
public interface IFactory {
IUser createUser();
ILogin createLogin();
}
实现IFactory接口,定义接口类型零件,以及如何去生成实例,如图制定生成产品的一种规范.
public class MysqlFactory implements IFactory {
@Override
public IUser createUser() {
return new MysqlUser();
}
@Override
public ILogin createLogin() {
return new MysqlLogin();
}
}
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
@Override
public ILogin createLogin() {
return new OracleLogin();
}
}
抽象零件接口
public interface ILogin {
void insert(String login);
void getLogin(int id);
}
public interface IUser {
void insert(String tableName);
IUser getUser(int uid);
}
具体产品
public class MysqlLogin implements ILogin {
@Override
public void insert(String login) {
System.out.println("登陆mysql");
}
@Override
public void getLogin(int id) {
System.out.println("获取mysql登陆id" + id);
}
}
public class MysqlUser implements IUser {
@Override
public void insert(String tableName) {
System.out.println("对 MySQL 里的 tableName 表插入了一条数据");
}
@Override
public IUser getUser(int uid) {
System.out.println("通过 uid 在 MySQL 里的 User 表得到了一条数据");
return null;
}
}
public class OracleLogin implements ILogin {
@Override
public void insert(String login) {
System.out.println("登陆oracle");
}
@Override
public void getLogin(int id) {
System.out.println("获取oracle登陆id" + id);
}
}
public class OracleUser implements IUser {
@Override
public void insert(String tableName) {
System.out.println("对 Oracle 里的 tableName 表插入了一条数据");
}
@Override
public IUser getUser(int uid) {
System.out.println("通过 uid 在 Oracle 里的 User 表得到了一条数据");
return null;
}
}
测试
public class Main {
public static void main(String[] args) {
// 只需要确定实例化哪一个数据库访问对象给factory
// IFactory factory=new MysqlFactory();
IFactory factory = new OracleFactory();
// 已与具体的数据库访问解除了耦合
IUser userOperation = factory.createUser();
userOperation.getUser(1);
userOperation.insert("user");
// 不同的产品
ILogin loginOperation = factory.createLogin();
loginOperation.insert("user");
loginOperation.getLogin(1);
}
}
3.模式分析
所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如 MysqlFactory 里可以生产 MysqlUser 以及 MysqlLogin 两个产品,而这两个产品又是属于一个系列的,因为它们都是属于MySQL数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 MysqlFactory 里就只可以生产一个 MysqlUser 产品。
4.优缺点
优点:
抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory=new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,例如我现在要更改以上代码的数据库访问时,只需要更改具体的工厂即可。
抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和ILogin,至于它是MySQl里的表还是Oracle里的表就不知道了
缺点:
但是任何的设计模式都有自身的缺陷都不是完美的,都有不适用的时候,例如抽象工厂模式虽然可以很方便的帮我们切换两个不同的数据库访问的代码。但是如果我们的需求来自于增加功能,例如我们还需要加多一个会员数据表 MemberData,那么我们就得先在以上代码的基础上,增加三个类:IMemberData,MysqlMemberData,OracleMemberData,还需要修改IFactory、MysqlFactory以及OracleFactory才可以完全实现。增加类还好说,毕竟我们是对扩展开放的,但是却要修改三个类,就有点糟糕了。
而且还有一个问题就是客户端程序类在实际的开发中,肯定不止一个,很多地方都会需要使用 IUser 或 ILogin ,而这样的设计,其实在每一个类的开始都需要写上 IFactory factory=new OracleFactory(); 这样的代码,如果我有一百个访问 User 或 Login 表的类,那不就得改一百个类?很多人都喜欢说编程是门艺术,但也的确如此,对于艺术我们应该去追求美感,所以这样大批量的代码更改,显然是非常丑陋的做法。
5.代码改进
用简单工厂来改进抽象工厂
将IFactory、MySQLFactory以及OracleFactory三个工厂类都抛弃掉,取而代之的是一个简单工厂类EasyFactory.
public class EasyFactory {
// 数据库名称
private static String db="MySQL";
// private static String db="Oracle";
public static IUser createUser(){
IUser user=null;
switch (db){
case "MySQL":
user=new MysqlUser();
break;
case "Oracle":
user=new OracleUser();
break;
}
return user;
}
public static ILogin createLogin(){
ILogin login=null;
switch (db){
case "MySQL":
login=new MysqlLogin();
break;
case "Oracle":
login=new OracleLogin();
break;
}
return login;
}
}
测试类:
public class Client {
public static void main(String[] args){
User user=new User();
Login login = new Login();
// 直接得到实际的数据库访问实例,而不存在任何依赖
IUser userOperation= EasyFactory.createUser();
userOperation.getUser(1);
userOperation.insert(user);
// 直接得到实际的数据库访问实例,而不存在任何依赖
ILogin loginOperation=EasyFactory.createLogin();
loginOperation.insert(login);
loginOperation.getLogin(1);
}
}
由于事先在简单工厂类里设置好了db的值,所以简单工厂的方法都不需要由客户端来输入参数,这样在客户端就只用 EasyFactory.createUser(); 和 EasyFactory.createLogin(); 方法来获得具体的数据库访问类的实例,客户端代码上没有出现任何一个 MySQL或Oracle 的字样,达到了解耦的目的,客户端已经不再受改动数据库访问的影响了。
用反射机制+简单工厂模式继续改进代码
但是我们都知道简单工厂也存在一个缺陷,例如我要增加一个SQL Server数据库的访问类,那么本来抽象工厂模式只需要增加一个SQLServerFactory工厂类就可以了,而简单工厂则需要在每个方法的switch中增加case条件了。
所以我们要考虑的是可以不可以不在代码里写明条件分支语句,而是根据字符串db的值来去某个地方找需要实例化的那个类,这样的话,我们就可以和switch语句say goodbye了。
而在Java中有一种技术可以做到这一点,那就是反射机制,有了反射机制我们只需要使用字符串就可以获取某个类的实例.
public class EasyFactory {
private static String packName = "org.zero01.product";
// 数据库名称,可替换成Oracle
private static String db = "Mysql";
// private static String db="Oracle";
public static IUser createUser() throws Exception {
String className = packName + "." + db + "User";
return (IUser)Class.forName(className).newInstance();
}
public static ILogin createLogin() throws Exception {
String className = packName + "." + db + "Login";
return (ILogin)Class.forName(className).newInstance();
}
}
项目比较大的话,就可以直接使用工厂方法模式了,那样只需要增加新的类即可,不需要对原有的代码进行改动,灵活性比简单工厂更强。所以在实际的项目中,我们应该根据情况来选择使用哪种设计模式,不然使用哪种模式也好,都有可能会导致设计过度或不足。
对于反射,可以通过配置文件获取参数,不需要修改其中代码.
反射机制+配置文件+简单工厂模式继续改进代码
ublic class EasyFactory {
private static String packName;
private static String db;
// 读取配置文件内容,初始化变量值
static {
try {
FileReader fileReader = new FileReader("app.json");//配置文件
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuffer config = new StringBuffer();
String s = null;
while ((s = bufferedReader.readLine()) != null) {
config.append(s);
}
bufferedReader.close();
JSONObject jsonObject = new JSONObject(config.toString());
packName = jsonObject.getString("packName");
db = jsonObject.getString("DB");
} catch (Exception e) {
e.printStackTrace();
}
}
public static IUser createUser() throws Exception {
String className = packName + "." + db + "User";
return (IUser) Class.forName(className).newInstance();
}
public static ILogin createLogin() throws Exception {
String className = packName + "." + db + "Login";
return (ILogin) Class.forName(className).newInstance();
}
}
几乎所有在用简单工厂的地方,都可以考虑利用反射机制来去除 switch case 或 if else 等条件分支语句,进一步解除分支判断带来的耦合.