1 定义
提供一个创建一系列相关的接口,而无需指定它们具体的类。
2 优点和缺点
2.1优点
易于交换产品系列,这是最大的优点。例如IFactory factory=new AccessFactory(),只在初始化的时候出现一次,这样改变一个应用的具体工厂变得很容易,只需要改变具体工厂即可使用不同的产品配置。如果现在要改变数据库访问,只需要修改具体工厂就行了。
-
它让具体的创建过程和客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。
-
是开放-封闭原则、依赖倒转原则的良好运用。其实,这一点和工厂模式一样。
2.2 缺点
通过第4章和5.2.1节,可知,如果要增加一个Project项目表,至少要添加3个类,IProject,SqlServerProject,AccessProject,还需要修改IFactory,SqlServerFactory,AccessFactory,极不方便。所以有了利用简单工厂改进抽象工厂。
3 UML图–以切换数据库场景为例
IUser是对user表的操作接口,SqlServerUser和AccessUser实现了IUser接口。类图中没有画出User表对应的类和Department表对应的类。
4 例子–切换数据库场景
4.1 场景
数据有2个来源,SqlServer和Access;产品有2个,User和Department。
下面给出针对上述需求的代码,采用的设计模式演化过程为:
- 抽象工厂模式
- 简单工厂模式+抽象工厂模式
- 反射+配置+抽象工厂模式
4.2 代码
4.2.1 代码(抽象工厂模式)
Main
public class Main {
public static void main(String[] args) {
User user=new User();
//IFactory factory=new SqlServerFactory();
IFactory factory=new AccessFactory();
IUser iu=factory.createUser();
iu.insert(user);
iu.getUser(0);
}
}
User
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;
}
}
Department
public class Department {
private int id;
private String deptName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
IFactory
public interface IFactory {
IUser createUser();
IDepartment createDepartment();
}
SqlServerFactory
public class SqlServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
AccessFactory
public class AccessFactory implements IFactory {
@Override
public IUser createUser() {
return new AccessUser();
}
@Override
public IDepartment createDepartment() {
return new AccessDepartment();
}
}
IUser
public interface IUser {
public void insert(User product);
public User getUser(int id);
}
IDepartment
public interface IDepartment {
public void insert(Department product);
public Department getDepartment(int id);
}
SqlServerUser
public class SqlServerUser implements IUser {
@Override
public void insert(User product) {
System.out.println("在sql server中给user表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在sql server中根据id得到user表一条记录");
return null;
}
}
SqlServerDepartment
public class SqlServerDepartment implements IDepartment {
@Override
public void insert(Department product) {
System.out.println("在sql server中给Department表增加一条记录");
}
@Override
public Department getDepartment(int id) {
System.out.println("在sql server中根据id得到Department表一条记录");
return null;
}
}
AccessUser
public class AccessUser implements IUser {
@Override
public void insert(User product) {
System.out.println("在access中给user表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在access中根据id得到user表一条记录");
return null;
}
}
AccessDepartment
public class AccessDepartment implements IDepartment {
@Override
public void insert(Department product) {
System.out.println("在access中给Department表增加一条记录");
}
@Override
public Department getDepartment(int id) {
System.out.println("在access中根据id得到Department表一条记录");
return null;
}
}
4.2.2 代码(简单工厂+抽象工厂)
由上面的抽象工厂可知,如果要增加一个Project项目表,至少要添加3个类,IProject,SqlServerProject,AccessProject,还需要修改IFactory,SqlServerFactory,AccessFactory,极不方便。所以有了利用简单工厂改进抽象工厂。抛弃了IFactory,SqlServerFactory,AccessFactory,取而代之的是DataAccess类
。
Main
//注意:本文件夹中是:简单工厂+抽象工厂
public class Main {
public static void main(String[] args) {
User user=new User();
//在DataAccess中内置了,用哪个数据库
IUser iu=DataAccess.createUser();
iu.insert(user);
iu.getUser(0);
}
}
DataAccess
//注意:本文件夹中是:简单工厂+抽象工厂
public class DataAccess {
private static String db="SqlServer";
//private static String db="Access";
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;
}
}
4.2.3 代码(反射+配置+抽象工厂)
如果要增加Oracle数据库访问的话,原来的抽象工厂只增加一个OracleFactory就可以,在简单工厂+抽象工厂中,需要在每个方法中的switch中添加相应的case。所以产生了反射+抽象工厂
。DataAccess类用反射
,代替IFactory,SqlServerFactory,AccessFactory
。
//反射核心
String className=path+"."+db+"User";
return (IUser)Class.forName(className).newInstance();
反射
需要根据字符串来决定使用哪个数据库
,所以字符串要放在配置文件中,这样切换数据库的时候,只需要修改配置文件即可
,不需要重新部署、启动程序
。
Main
//注意:本文件夹中是:反射+抽象工厂
public class Main {
public static void main(String[] args) throws Exception {
User user=new User();
//在DataAccess中内置了,用哪个数据库
IUser iu=DataAccess.createUser();
iu.insert(user);
iu.getUser(0);
}
}
配置文件 Appconfig.properties
//db=SqlServer
db=Access
AppConfigHelper
public class AppConfigHelper {
private static AppConfigHelper appConfigHelper;
private static Properties prop = new Properties();
private AppConfigHelper() {
readProperties();
}
private static AppConfigHelper getSingle() {
if (appConfigHelper != null) {
appConfigHelper = null;
}
appConfigHelper = new AppConfigHelper();
return appConfigHelper;
}
public static String getDB() {
getSingle();
return prop.getProperty("db");
}
private void readProperties() {
//这里filepath得到的是项目目录:F:\bupt\project\leetcode\
//String filepath = System.getProperty("user.dir");
//这里得到的是项目编译后的class目录:F:\bupt\project\leetcode\out\production\leetcode\
String filepath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
// 将文件路径中含有的%20替换为空格,避免出现java.io.fileNotFoundException
filepath = filepath.replaceAll("%20", " ");
try {
//这里补充完整AppConfig.properties的目录
filepath += "designPattern/AbstractFactoryPattern/ReflectionAddAbsFac/";
prop.load(new FileInputStream(filepath + "AppConfig.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
DataAccess
//注意:本文件夹中是:反射+配置+抽象工厂
public class DataAccess {
private static final String path="designPattern.AbstractFactoryPattern.AbsFactoryPattern";
private static String db="SqlServer";
private static void setDB() {
db=AppConfigHelper.getDB();
}
//利用反射,根据字符串确定实例化哪个类
public static IUser createUser() throws Exception{
setDB();
String className=path+"."+db+"User";
return (IUser)Class.forName(className).newInstance();
}
//利用反射,根据字符串确定实例化哪个类
public static IDepartment createDepartment() throws Exception{
setDB();
String className=path+"."+db+"Department";
return (IDepartment)Class.forName(className).newInstance();
}
}
5 工厂模式和抽象工厂模式的适用场景
- 工厂模式 适合具体功能类ConcreteProductX只有一个系列的情况,例如只有一个User类和User操作类的时候。
- 抽象工厂模式 适合一个具体功能类ConcreteProductX有至少两个系列的情况,现在有很多表,而sql server和access又是不同的分类,抽象工厂模式 适合解决涉及到多个产品系列的问题。