我们都知道在web开发时经常使用三层架构(web、service、dao),每一层有着自己的实现类,而通过对实现类进行抽取方法形成接口,每一层通过接口进行上下层之间的耦合。例如在业务service层和数据访问dao层之间,当dao层写好了对数据库的增删改查方法时,抽取成dao接口,而在service如果要调用dao层的方法就只要使用dao接口即可,但是关键是在service层使用dao接口的时候,如何获取dao接口的实现对象是个问题。
案例:在数据访问dao层,编写好了一个对数据库中表user的增删查改方法,封装于UserDaoImpl实现类中,将UserDaoImpl实现类中的方法抽取出来成UserDao接口,那么如果在service中要使用User对象,那么就必须要使用到dao层中有关User的操作,由多态的灵活性,在service层中只要使用UserDao接口即可,但是光有一个接口引用对象没有用,我们必须要有UserDaoImpl这样的真正的实现类才能构建(new)出对象,难道要在service层使用UserDao dao = new UserDaoImpl();这样的代码?这样明显使dao层的实现类侵入了service层中,如果将来我们的实现类从UserDaoImpl变成了UserDaoJdbcImpl或者UserDaoXmlImpl这样的岂不是要在service层的代码中改动?
如何对不同层的代码进行解耦,关乎整个应用的灵活性,下面我们就来使用工厂模式来结局这个问题。
创建一个工程,为了简洁说明上面的例子,我们就创建四个类或接口就好了,结构如下图所示:
在com.fjdingsd.daoimpl包中的dao层的实现类,这里面封装了一个对User对象的增删改查的具体实现方法(方法内容略):
1 package com.fjdingsd.daoimpl; 2 public class UserDaoImpl implements UserDao { 3 @Override 4 public void insert(){ 5 。。。//添加User对象 6 } 7 8 @Override 9 public void delete() { 10 。。。//删除User对象 11 } 12 13 @Override 14 public void update() { 15 。。。//修改User对象 16 } 17 18 @Override 19 public void find() { 20 。。。//查找User对象 21 } 22 }
将该dao对User的实现类方法抽取到UserDao接口中去:
1 package com.fjdingsd.dao; 2 public interface UserDao { 3 public abstract void insert(); 4 public abstract void delete(); 5 public abstract void update(); 6 public abstract void find(); 7 }
如果没有使用工厂模式,那么因为没什么好的方法,所以只能在service层的实现类中直接使用UserDaoImpl来构建对象,但是我们这里使用工厂模式,来避免这个问题。使用工厂模式为了便于灵活性,我们将dao层接口和dao层的实现类在配置文件factory.properties中定义,以dao接口名作为关键字,以dao实现类的全名(包名+类名)作为关键字的值。以dao层中对User对象为例,在factory.properties中定义如下:
UserDao=com.fjdingsd.dao.impl.UserDaoImpl
使用这样配置的好处在于以后对于UserDao接口,如果我们想更换实现类,只要改动配置文件即可,代码中完全不需要修改。
在工厂类DaoFactory中的代码如下:
1 package com.fjdingsd.factory; 2 public class DaoFactory { 3 4 private static DaoFactory instance = new DaoFactory(); 5 private Properties config = new Properties(); 6 7 private DaoFactory(){ 8 InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("factory.properties"); //读取配置文件装载进输入流 9 try { 10 config.load(in); //将配置文件封装进Properties对象中 11 12 } catch (IOException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 public static DaoFactory getInstance() { 18 return instance; 19 } 20 21 public <T> T createDao(Class<T> clazz) { 22 String interfaceName = clazz.getSimpleName();//获取配置文件中的接口关键字 23 String className = config.getProperty(interfaceName); //根据接口名获取配置文件中的具体实现类完整名称 24 try { 25 T bean = (T) Class.forName(className).newInstance();//使用反射创建实现类的一个实例对象 26 return bean; 27 } catch (Exception e) { 28 throw new RuntimeException(e); 29 } 30 } 31 }
这样在service层的实现类中以dao层接口引用具体dao层的实现类就不需要直接创建对象,直接使用工厂模式加泛型即可,如下代码所示:
1 package com.fjdingsd.service.impl; 2 public class BussinessServiceImpl { 3 4 //UserDao uDao = new UserDaoImpl(); 以前未使用工厂模式加泛型之前的写法 5 UserDao uDao = DaoFactory.getInstance().createDao(UserDao.class); 6 。。。//其他代码,此处略 7 }
分析:
工厂模式负责一部分接口对象的实现类生成,建议使用单例模式,上面的示例也是这样。工厂模式使用单例的好处在于如果要修改代码,那么只要找工程即可,如果每一个接口的实现类都配一个工厂,那么工厂会太多,不利于程序的简洁。
在工厂中,为某个接口创建实现类对象,我们使用 配置文件+泛型 的方法,配置文件前面已经说过了,如果想更换某个接口的实现类只要修改配置文件即可。而使用泛型,可以很优雅地避免对某个具体的接口都要写一个方法来创建对象,如上例在工厂中的createDao方法,只要根据配置文件信息就可以为接口获取一个对应的实现类,同时要注意在配置文件中的关键字和值一定要是和已经定义好的接口名和类名匹配。请好好品尝消化上面的例子。