注:示例来自《大话设计模式》
现有如下需求 写一个基本的数据访问程序 数据库用SqlServer 简单代码实现如下
用户类
package Test15;
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;
}
}
操作表类
package Test15;
public class SqlserverUser {
public void Insert(User user)
{
System.out.println("在Sqlserver中给User表增加一条记录");
}
public User GetUser(int id)
{
System.out.println("在Sqlserver中根据ID得到User表一条记录");
return null;
}
}
客户端代码
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
SqlserverUser su = new SqlserverUser();
su.Insert(user);
su.GetUser(1);
}
}
上面的写法 客户端与SqlServer强耦合 如果想要更换数据库为Access 该怎么办呢
下面使用工厂方法模式进行重构 代码如下
IUser接口
package Test15;
public interface IUser {
void Insert(User user);
User GetUser(int id);
}
SqlserverUser类
package Test15;
public class SqlserverUser implements IUser {
@Override
public void Insert(User user)
{
System.out.println("在Sqlserver中给User表增加一条记录");
}
@Override
public User GetUser(int id)
{
System.out.println("在Sqlserver中根据ID得到User表一条记录");
return null;
}
}
AccessUser类
package Test15;
public class AccessUser implements IUser {
@Override
public void Insert(User user)
{
System.out.println("在Access中给User表增加一条记录");
}
@Override
public User GetUser(int id)
{
System.out.println("在Access中根据ID得到User表一条记录");
return null;
}
}
IFactory接口
package Test15;
public interface IFactory {
IUser CreateUser();
}
SqlServerFactory类
package Test15;
public class SqlServerFactory implements IFactory {
@Override
public IUser CreateUser() {
return new SqlserverUser();
}
}
AccessFactory类
package Test15;
public class AccessFactory implements IFactory {
@Override
public IUser CreateUser() {
return new AccessUser();
}
}
客户端代码
package Test15;
public class Program {
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(1);
}
}
现在如果要换数据库 只需要把new SqlServerFactory()改成new AccessFactory()
但是问题还没有完全解决 数据库里不可能只有一个User表 很可能有其他表 比如增加部门表 此时该怎么办呢 修改代码如下
部门类
package Test15;
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;
}
}
IDepartment接口
package Test15;
public interface IDepartment {
void Insert(Department department);
Department GetDepartment(int id);
}
SqlserverDepartment类
package Test15;
public class SqlserverDepartment implements IDepartment {
public void Insert(Department department)
{
System.out.println("在Sqlserver中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
System.out.println("在Sqlserver中根据ID得到Department表一条记录");
return null;
}
}
AccessDepartment类
package Test15;
public class AccessDepartment implements IDepartment {
public void Insert(Department department)
{
System.out.println("在Access中给Department表增加一条记录");
}
public Department GetDepartment(int id)
{
System.out.println("在Access中根据ID得到Department表一条记录");
return null;
}
}
IFactory接口
package Test15;
public interface IFactory {
IUser CreateUser();
IDepartment CreateDepartment();
}
SqlServerFactory类
package Test15;
public class SqlServerFactory implements IFactory {
@Override
public IUser CreateUser() {
return new SqlserverUser();
}
@Override
public IDepartment CreateDepartment()
{
return new SqlserverDepartment();
}
}
AccessFactory类
package Test15;
public class AccessFactory implements IFactory {
@Override
public IUser CreateUser() {
return new AccessUser();
}
@Override
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}
客户端代码
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
Department dept = new Department();
//IFactory factory = new SqlServerFactory();
IFactory factory = new AccessFactory();
IUser iu = factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = factory.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
}
}
不知不觉间 我们已经重构出了抽象工厂模式
抽象工厂模式 提供一个创建一系列相关或相互依赖对象的接口 而无需指定它们具体的类
抽象工厂模式最大的好处便是易于交换产品系列 由于具体工厂类 例如IFactory factory = new AccessFactory() 在一个应用中只需要在初始化的时候出现一次 这就使得改变一个应用的具体工厂变得非常容易 它只需要改变具体工厂即可使用不同的产品配置
第二大好处是 它让具体的创建实例过程与客户端分离 客户端是通过它们的抽象接口操纵实例 产品的具体类名也被具体工厂的实现分离 不会出现在客户代码中
缺点 如果需要增加新的表 那么就要新增三个类 并且要修改三个工厂类 这样大批量的改动 显然是非常丑陋的做法
下面我们用简单工厂来改进抽象工厂 代码如下
DataAccess类
package Test15;
public class DataAccess {
private static final String db = "Sqlserver";
//private static final 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;
}
}
客户端代码
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
}
}
现在客户端已经不再受改动数据库访问的影响了 但是如果我需要增加Oracle数据库访问 本来抽象工厂只增加一个OracleFactory工厂类就可以了 现在就比较麻烦了
下面我们用反射+抽象工厂的方式进行重构 代码如下
修改DataAccess类
package Test15;
public class DataAccess {
private static final String AssemblyName = "Test15";
private static final String db = "Sqlserver";
//private static final String db = "Access";
public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
String className = AssemblyName+"."+db+"User";
Class<IUser> forName = (Class<IUser>) Class.forName(className);
return forName.newInstance();
}
public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
String className = AssemblyName+"."+db+"Department";
Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className);
return forName.newInstance();
}
}
现在如果增加Oracle数据访问 我们只需更改private static final String db = “Sqlserver”;为private static final String db = “Oracle”;
但是 总感觉还是有点遗憾 因为在更换数据库访问时 我还是需要去改程序重编译
下面我们用反射+配置文件的方式进行重构
添加一个sql.properties文件 内容如下
db=Sqlserver
DataAccess类
package Test15;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class DataAccess {
private static final String AssemblyName = "Test15";
public static String getDb() throws IOException {
Properties properties = new Properties();
String db = "";
// 使用BufferedReader加载properties配置文件生成对应的输入流
BufferedReader bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.dir")+"/src/test/java/Test15/sql.properties"));
// 使用properties对象加载输入流
properties.load(bufferedReader);
//获取key对应的value值
db = properties.getProperty("db");
return db;
}
public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
{
String className = AssemblyName+"."+getDb()+"User";
Class<IUser> forName = (Class<IUser>) Class.forName(className);
return forName.newInstance();
}
public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
{
String className = AssemblyName+"."+getDb()+"Department";
Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className);
return forName.newInstance();
}
}
现在如果需要更换数据库 只需要修改配置文件就可以了 从这个角度上说 所有在用简单工厂的地方 都可以考虑用反射技术来去除switch或if 解除分支判断带来的耦合