您的位置:首页 > 编程语言

第四篇、抽象工厂模式的优化

2017-02-17 15:25 253 查看
在上一篇的博文中,我们研究了抽象工厂,我们简单的回顾一下:

首先,抽象工厂是为了应对产品簇的概念而生的。

其次,它使我们在不影响现有产品等级结构的基础上,方便的在不同产品系列当中进行切换。

最后,它封装了产品对象的创建过程,使之与客户端进行分离,无需客户端考虑他的组装过程。

但是,如果需求来自功能的增加,我们不仅要增加新的功能父类与产品系列子类,还需修改工厂接口以及所有子类工厂。

——————————————————————————————————————————————————————————————————

那我们有什么方式可以对抽象工厂进行优化呢,大家随我一起来看一下。

用简单工厂+抽象工厂的方式进行实现:

首先,对于产品的子类,我们不做修改

先是IUser以及其子类

public abstract class IUser {
public abstract void insert(User user);
public abstract User getUser(String id);
}
public class SqlServerUser extends IUser {

@Override
public void insert(User user) {
// ......
System.out.println("在SqlServer中插入User成功");
}

@Override
public User getUser(String id) {
User u = null;
// ......
System.out.println("在SqlServer中查询到了User");
return u;
}
}

public class AccessUser extends IUser {

@Override
public void insert(User user) {
// ......
System.out.println("在Access中插入User成功");
}

@Override
public User getUser(String id) {
User u = null;
// ......
System.out.println("在Access中查询到了User");
return u;
}

}
然后是IDepartment以及其子类
public abstract class IDepartment {
public abstract void insert(Department deprecated);
public abstract Department getDepartment(String id);
}

public class SqlServerDepartment extends IDepartment {

@Override
public void insert(Department department) {
// ......
System.out.println("在SqlServer中插入Department成功");
}

@Override
public Department getDepartment(String id) {
Department d = null;
// ......
System.out.println("在SqlServer中查询到了Department");
return d;
}
}

public class AccessDepartment extends IDepartment {

@Override
public void insert(Department deprecated) {
// ......
System.out.println("在Access中插入Department成功");
}

@Override
public Department getDepartment(String id) {
Department d = null;
// ......
System.out.println("在Access中查询到了Department");
return d;
}

}


我们要修改的是工厂类,去掉IFactory以及他的子类SqlServerFactory和AccessFactory。用一个简单工厂类DataFactory代替。在写DataFactory的代码之前,我们先来简单了解一下反射,根据我们代码的需要,这里只简单讲解一下通过反射来实例化对象。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。我们知道,类的实例化过程,就是通过new的方式调用它的构造函数,从而获得这个类的对象,那么这个构造函数就是这个类的一个特殊方法,我们就可以通过反射来调用,从而获取对象:

1、假如我们有一个学生类

public class Student {
String name = "新生";

public Student() {
super();
}

public Student(String name) {
super();
this.name = name;
}

}

2、然后我们通过反射来实例化这个学生类,从而获得学生对象
Student stu = null;
try {
stu = (Student) Class.forName("my.demo.milltly.jd_cx.Student").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

System.out.println(stu.name);

输出结果为:新生。我们通过Class.forName()来获得这个Student类,其中作为参数的字符串my.demo.milltly.jd_cx.Student是这个类的全路径名,newInstance就是调用这个类的无参构造函数。
那么我们要调用它的有参构造函数,使之实例化对象的同时,赋予它名字,要怎么做呢。

Student stu = null;
Class[] cla = { String.class };
Object[] ob = { "小明" };

try {
stu = (Student) Class.forName("my.demo.milltly.jd_cx.Student").getDeclaredConstructor(cla).newInstance(ob);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

System.out.println(stu.name);
首先,我们要声明两个数组,Class数组是用来放置传入参数的类型,Object数组是对应参数的实际值。在调用的时候,我们需要先调用getDeclaredConstructor,将Class数组传入,以获取类中对应参数的构造器,再通过newInstance将需要赋值的参数数组传给构造器,从而获取对象。

好了,回来看一下DataFactory要怎么写吧

public class DataFactory {
private static String className_User = "my.demo.milltly.jd_cx.SqlServerUser";
private static String className_Department = "my.demo.milltly.jd_cx.SqlServerDepartment";

// private static String className_User = "my.demo.milltly.jd_cx.AccessUser";
// private static String className_Department = "my.demo.milltly.jd_cx.AccessDepartment";

public static IUser createUser() {
IUser u = null;
try {
u = (IUser) Class.forName(className_User).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return u;
}

public static IDepartment createDepartment() {
IDepartment d = null;
try {
d = (IDepartment) Class.forName(className_Department).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return d;
}
}
好了,这样我们就可以获得所需要的对象了,我们在客户端调用一下:

4000
IUser u = DataFactory.createUser();
u.insert(new User());
User user = u.getUser("1");

IDepartment d = DataFactory.createDepartment();
d.insert(new Department());
Department department = d.getDepartment("1");

对于一个应用,我们要用的数据库,一般都是一个,应该不会出现一个程序即用Sql Server又用Access的,所以,我将所需要用的数据库标识写到了DataFactory中,免去了客户端调用Create方法的时候还要传递参数的过程,这样,需要切换数据库的时候,我只需要将上面声明的标识字符串替换就好了,无需去改客户端代码。
通过反射技术,如果需要增加新的数据库Oracle的时候,我只需要增加新的功能子类,然后将字符串替换成Oracle的就好了,避免了之前第一篇中简单工厂类修改switch...case的尴尬。

如果需求来自功能的增加,比如添加Project表,我们也无需像之前抽象工厂那样,修改IFactory及其子类。而只需在DataFactory中增加新的create方法,无需对已有方法进行修改,即可完成功能增加。是不是比之前设计方便了很多,这也是反射的魅力所在。

有些朋友会说,切换数据库的时候,我还是要修改代码,修改就要重新编译,能不能不修改代码呢。可以,因为我们是通过字符串变量来实例化对象,变量意味着,我可以在运行时来确定它的值,而无需在编译期就要指定它的值,这就给了我们灵活应用的空间。

用配置文件来优化

首先,书写配置文件a.properties

userclass = my.demo.milltly.jd_cx.SqlServerUser
departmentclass = my.demo.milltly.jd_cx.SqlServerDepartment
然后通过读取配置文件信息,来实例化对象

public class DataFactory {
private static String getUserClassName() {
Properties prop = new Properties();
try {
prop.load(DataFactory.class.getClassLoader().getResourceAsStream("a.properties"));
} catch (IOException e) {
e.printStackTrace();
}
String userClass = prop.getProperty("userclass");
return userClass;
}

private static String getDepartmentClassName() {
Properties prop = new Properties();
try {
prop.load(DataFactory.class.getClassLoader().getResourceAsStream("a.properties"));
} catch (IOException e) {
e.printStackTrace();
}
String departmentClass = prop.getProperty("departmentclass");
return departmentClass;
}

public static IUser createUser() {
IUser u = null;
try {
u = (IUser) Class.forName(getUserClassName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return u;
}

public static IDepartment createDepartment() {
IDepartment d = null;
try {
d = (IDepartment) Class.forName(getDepartmentClassName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return d;
}
}这样,当发生数据库切换的时候,我只需修改配置文件为所需数据库功能类,就可完成切换,无需修改代码,是不是更方便了。好了,这次就先说到这里吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息