工廠模式 + 單例模式實戰


一、緒論

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
已保存賬戶~!

三、小結

工廠模式基礎上再結合單例模式的編程思想,不但可以降低耦合,同時也可以節省內存消耗,為程序提供更高的魯棒性。


人若無名,專心練劍~!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM