一、绪论
1、Tips
这篇文章是基于上篇文章 《工厂模式为 MVC 解耦》之上的后续,建议先看上篇文章。
2、提出问题
上篇文章使用工厂模式和反射为保存账户的功能进行解耦,可以决解缺少某个类时编译不出错,但是运行抛异常,从而降低耦合。
但是工厂模式还是有一定的问题的,我们先来看下在 AccountDemo 中,连续创建五次的 AccountServiceImpl 的对象内存地址分别是什么。
- 修改下 AccountDemo 中的代码,其他不变,具体如下:
public class AccountDemo {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
// as.saveAccount();
}
}
//*************************************************
// 运行结果
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@677327b6
wiki.laona.service.impl.AccountServiceImpl@14ae5a5
wiki.laona.service.impl.AccountServiceImpl@7f31245a
wiki.laona.service.impl.AccountServiceImpl@6d6f6e28
- 从运行结果可以明显看出来,五个对象的内存地址都是不一样的,那就意味着每次调用 BeanFactory.getBean 方法都会创建一个新的对象。这样耦合是解决了但是内存开销也随之变大了。
3、刨析一下
原因其实这也不能理解,在 getBean 方法中,每次获取 bean 的对象实例都是通过反射 newInstance 新建一个实例,所以每次调用都会在内存中 new 一个新的对象,导致了内存消耗,这就是多例的存在的问题。解决这个问题,我们可以通过单例的思想解决。有关单例模式的内容可以查看博客《Java 设计模式 -- 单例模式》, 另外 getBeam() 方法如下:
/**
* 通过资源名获取类对象
*
* @param beanName 资源名
* @return {@link Object} 类对象
* @throws ClassNotFoundException {@link ClassNotFoundException} 未找到类对象异常
*/
public static Object getBean(String beanName) {
Object bean = null;
String res = props.getProperty(beanName);
try {
bean = Class.forName(res).newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return bean;
}
二、工厂模式结合单例模式
改造工厂模式,我们只需要改动 BeanFactory 类就行了。
1、主要思路
- 通过 Map 保存 AccountService 和 AccountDao 的实例对象
- 在 getBean 中返回 Map 中保存的AccountService 和 AccountDao 的实例对象。
2、 代码实现
2.1、建立保存
- 代码实现:
/**
* 保存 AccountService 和 AccountDao 的实例对象的 Map
*/
private static Map<String, Object> beans;
2.2、把实例对象保存到 Map 中
- 这一部分在静态代码块中实现,代码如下:
static {
try {
props = new Properties();
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(is);
// 初始化 Map
beans = new HashMap<>();
// 获取 prop 中的实例对象实例
Enumeration<Object> keys = props.keys();
while (keys.hasMoreElements()) {
// 获取 key
String key = keys.nextElement().toString();
// 获取 properties 文件中的实例路径
String beanPath = props.getProperty(key);
// 反射获取实例对象
Object val = Class.forName(key).newInstance();
// 保存 key 和 value 到 beans 中
beans.put(key, val);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化 Properties 文件失败~!");
}
}
2.3、在 getBean 方法中返回对象实例
- 通过 key 获取 Map 的对象实例,返回便可。
- 代码实现如下:
/**
* 通过名字获取实例对象
* @param beanPath 实例名称
* @return {@link Object} 实例对象
*/
public static Object getBean(String beanPath) {
return beans.get(beanPath);
}
3、测试
3.1、集成测试
- 因为直接重写了 getBean 方法,所以在 AccountDemo 中无需修改,直接运行,此时获取的内存地址是一样,代码如下:
public class AccountDemo {
public static void main(String[] args) {
IAccountService as = null;
for (int i = 0; i < 5; i++) {
as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
as.saveAccount();
}
}
//*********************************
// 运行结果
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
wiki.laona.service.impl.AccountServiceImpl@1540e19d
已保存账户~!
三、小结
工厂模式基础上再结合单例模式的编程思想,不但可以降低耦合,同时也可以节省内存消耗,为程序提供更高的鲁棒性。
人若无名,专心练剑~!
网友评论