一、引入
工厂模式是我们最常用的实例化对象模式了,它是用工厂中的方法代替new创建对象的一种设计模式。
我们以Mybatis的SqlSession接口为例,它有一个实现类DefaultSqlSession,如果我们要创建该接口的实例对象:
SqlSession sqlSession = new DefaultSqlSession();
可是,实际情况是,通常我们都要在创建SqlSession实例时做点初始化的工作,比如解析XML,封装连接数据库的信息等等。
在创建对象时,如果有一些不得不做的初始化操作时,我们首先到的是,可以使用构造函数,这样生成实例就写成:
SqlSession sqlSession = new DefaultSqlSession(传入配置文件的路径);
但是,如果创建sqlSession实例时所做的初始化工作不是像赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(这时候就需要代码Refactor重构)。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build("SqlMapConfig.xml");
SqlSession sqlSession = factory.openSession();
所以,Mybatis框架在使用时为我们提供了SqlSessionFactory工厂类,通过openSession()方法获取到SqlSession对象。
二、简单工厂
本篇主要侧重优化,简单工厂内容不赘述了,简单来说就是通过方法调用来获得对象。
- 前提类和接口
//创建账户的业务层接口
public interface AccountService {
//保存账户
void saveAccount();
}
//创建账户的业务层实现类
public class AccountServiceImpl implements AccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
//创建账户的持久层接口
public interface AccountDao {
//保存账户
void saveAccount();
}
//创建账户的持久层实现类
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
- 工厂类
public class BeanFactory {
//传入接口名,返回实现类对象
public static Object getBean(String InterfaceName) {
if (InterfaceName.equals("AccountDao")) {
return new AccountDaoImpl();
} else if(InterfaceName.equals("AccountService")){
return new AccountServiceImpl();
}
}
}
- 测试
public class AccountTest {
public static void main(String[] args) {
//创建业务层对象, 调用save方法,不在需要new对象了
//AccountService accountService = new AccountServiceImpl();
AccountService accountService = (AccountService) BeanFactory.getBean("AccountService");
accountService.saveAccount();
AccountDao accountDao = (AccountDao) BeanFactory.getBean("AccountDao");
accountDao.saveAccount();
}
}
三、优化:进一步解耦合(重点)
除了创建对象时,不再使用new的方式以外,工厂类中还可以有进一步的优化空间
- 在我们使用一些不是频繁创建的对象时,采用反射的方式创建对象显然更加合理。而反射创建时需要提供创建类的全限定类名,这个名称如果写在java代码中,造成的结果就是修改仍然避免不了修改源码。所以,我们需要使用配置文件,把要创建类的全限定类名用配置文件配置起来。
- 解耦的思路总结:
- 第一:通过工厂类
- 第二:使用反射创建对象
- 第三:创建对象用到的全限定类名用配置文件配置起来
还是继续用上面的类和接口以及测试程序,只需要将工厂更新以及新增xml配置文件即可
那么具体的优化步骤是什么呢?
- Bean工厂优化步骤:
- 解析xml,获取所有对象的全限定类型(全名称)
- 反射创建对象,存储到容器中,使用map集合(键:接口名 值:接口实现类对象)
- 用户需要对象时,从容器中根据键interface直接获取对应值即可
- beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 配置 service -->
<bean interface="AccountService" fullClassName="com.zstu.service.impl.AccountServiceImpl"></bean>
<!-- 配置 dao -->
<bean interface="AccountDao" fullClassName="com.zstu.dao.impl.AccountDaoImpl"></bean>
</beans>
- 优化后的工厂类
public class BeanFactory {
// (键:接口名 值:接口实现类对象) : 反射创建好的对象
private static Map<String,Object> map = new HashMap<>();
//静态代码块:类加载时执行,只会执行一次
static {
//解析xml,获取所有对象的全限定类型(全名称)
SAXReader reader = new SAXReader();
try {
//获取文档对象
Document doc = reader.read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
//获取根节点
Element rootElement = doc.getRootElement();
//获取根节点下的所有的子节点
List<Element> elements = rootElement.elements();
//遍历
for (Element element : elements) {
String interfaceValue = element.attributeValue("interface");
String fullClassNameValue = element.attributeValue("fullClassName");
//反射创建对象,存储到容器中
Class clazz = Class.forName(fullClassNameValue);
Object obj = clazz.newInstance();
//存储到容器中,使得以后可以直接通过接口名获取对象
map.put(interfaceValue, obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//传入接口名,返回实现类对象
public static Object getBean(String id){
return map.get(id);
}
}
- 测试程序
public class AccountTest {
public static void main(String[] args) {
//创建业务层对象, 调用save方法,不在需要new对象了
//AccountService accountService = new AccountServiceImpl();
AccountService accountService = (AccountService) BeanFactory.getBean("AccountService");
accountService.saveAccount();
AccountDao accountDao = (AccountDao) BeanFactory.getBean("AccountDao");
accountDao.saveAccount();
}
这样的好处是,程序中不仅没有new对象,并且将耦合性降到最低。
对于配置文件的解析,这里是xml文件,使用的是dom4j包解析的,如果是properties文件的解析,可以看我之前写的一篇文章:传送门:读取properties配置文件的几种方法