一、緒論
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
已保存賬戶~!
三、小結
工廠模式基礎上再結合單例模式的編程思想,不但可以降低耦合,同時也可以節省內存消耗,為程序提供更高的魯棒性。
人若無名,專心練劍~!