做过web开发的同行都知道,大多数工作都是一些对数据库的曾删改查工作。如果使用了Hibernate那么就不用考虑更换数据库会带来的麻烦。但是如果没有使用hibernate数据库,那么网站开发完之后如果要更换数据库就会有很多的工作量要做。因为不同的数据库有可能一些操作不一样或者官方提供的API等等一些差异,那么如何解决这样的问题呢?
之前我写的数据库访问程序都是非常简单的,没有涉及到设计模式,即使使用了面向接口编程的技术也只是为了分层的方便。比如:
定义接口IUserDao,里面定义一些增删改查的功能,然后写一个实现类UserDao实现该接口中的方法;这时候如果要更换数据库,那么UserDao这个实现类就要重写,而且调用者也要做相应的改变。后来我看了大话设计模式,里面是用工厂方法模式(工厂方法模式就是定义一个用于创建对象的接口,由子类决定创建哪一个对象)解决的,看了之后现总结如下;
定义IUserDao接口,封装操作user表的方法;然后分别定义实现类:
接口:
public interface IUserDao {
public void save(User user);
public User get(int id);
}
实现类:
public class SqlUserDao implements IUserDao {
@Override
public void save(User user) {
System.out.println("add a user into sql server database");
}
@Override
public User get(int id) {
System.out.println("get a user from the sql server database");
return null;
}
}
实现类:
public class AccessUserDao implements IUserDao {
@Override
public void save(User user) {
System.out.println("add a user into access database");
}
@Override
public User get(int id) {
System.out.println("get a user from access database");
return null;
}
}
接下来使用工厂方法,首先定义一个工厂接口,该接口用于创建访问user表的对象,至于是那么数据库的对象由实现类决定:
工厂接口:
public interface IDBFactory {
public IUserDao createDB();
}
工厂实现类(操作sql server数据库中的表):
public class SqlFactory implements IDBFactory {
@Override
public IUserDao createDB() {
return new SqlUserDao();
}
}
工厂实现类(操作Access数据库中的表):
public class AccessFactory implements IDBFactory {
@Override
public IUserDao createDB() {
return new AccessUserDao();
}
}
在客户端使用的时候定义工厂的引用,然后赋值为具体的工厂实现类;然后使用该引用调用createDB方法得到相应的操作表的对象即可:
public class MainClass {
/**
* @param args
*/
public static void main(String[] args) {
User user = new User();
//对sql server数据库中的user表进行操作
IDBFactory dbFactory = new SqlFactory();
IUserDao userDao = dbFactory.createDB();
userDao.save(user);
userDao.get(1);
//对access数据库中的user表进行操作,把SqlFactory更换为AccessFactory即可
}
}
在这里我们讨论的只是数据库中的一个表,如果要是再增加一个表的话又该如何设计类呢?比如说以前我写的员工在线帮助系统中,还有一个请求表request。要实现对request表的访问,需要定义一个访问request的抽象接口,然后根据数据库生成相应的实现类即可;除此之外要在抽象工厂接口中再增加一个创建对象的方法;这样在客户端中通过改变IDBFactory dbFactory = new SqlFactory();在数据库之间随意的切换;
例如要访问Access数据库中的request表:
public class MainClass {
/**
* @param args
*/
public static void main(String[] args) {
User user = new User();
Request request = new Request();
//对sql server数据库中的user表进行操作
/*IDBFactory dbFactory = new SqlFactory();
IUserDao userDao = dbFactory.createUserDao();
userDao.save(user);
userDao.get(1);*/
//对access数据库中的request表进行操作
IDBFactory dbFactory = new AccessFactory();//与具体的数据库解耦
IRequestDao requestDao = dbFactory.createRequestDao();
requestDao.save(request);
requestDao.getRequest(1);
}
}
说实话我也不知道设计模式什么时候使用以及怎么使用是合适的,比如这里讲解的抽象工厂模式中,当增加一个表的话,要增加三个类;而且还要修改工厂类这样一来就着实麻烦;除此之外,如果要在很多的地方都使用了数据库访问程序,那么很多的地方都将会有下面这段程序:
/*IDBFactory dbFactory = new SqlFactory();
IUserDaouserDao = dbFactory.createUserDao();
userDao.save(user);
userDao.get(1);*/
这时如果要在数据库之间切换程序,那么仍然要更改很多的地方,这样大批量的程序更改无疑是一种很可怕的事情哦。在大话设计模式中是这样解决这个问题的:使用简单工厂模式改进,取消IDBFactory这个抽象的工厂接口,定义一个类使用switch分支语句产生具体的对象;至于怎么写需要读者自己去想了,但是需要达到能在数据库之间随意的切换而又不会产生大量的改动的目的。
我下面给出大话设计模式中的解答方法,使用简单工厂改进以上的程序。
程序代码如下:
public class DataAccess {
//private static String sign = "sqlserver";
private static String sign = "access";
public static IUserDao createUser() {
IUserDao userDao = null;
switch (DBEnum.getUpCase(sign)) {
case SQLSERVER:
userDao = new SqlUserDao();break;
case ACCESS:
userDao = new AccessUserDao();break;
}
return userDao;
}
public static IRequestDao createRequest() {
IRequestDao requestDao = null;
switch (DBEnum.getUpCase(sign)) {
case SQLSERVER:
requestDao = new SqlRequestDao();break;
case ACCESS:
requestDao = new AccessRequestDao();break;
}
return requestDao;
}
}
这里面的两个方法是以两个表为大的分类,通过改变sign变量的值在方法中利用switch分支语句决定是哪一个数据库。但是这样做的也有很大的麻烦。这时候如果要增加oracle数据库,当然要增加实现类,这是必须的,因为这是扩展。但是除此之外还要在DataAccess类的每一个方法中增加一个case分支。如果有很多的表的话这样的改动也是不合适的,有违开闭原则中对修改关闭一条。那么怎么进一步修改呢?在大话设计模式中提到了使用反射的技术,提供类的全称来自动的实例化具体的类,也就是把程序由编译时转化为运行时。