淺談工廠模式中的解耦、單例多例問題及思考--觀IT黑馬視頻有感/入門Spring之前的準備工作

學習Spring的小夥伴一定對IoC容器或多或少有些瞭解了,IoC總結爲一句話就是爲了方便解耦。我們都知道Java中要使用類必須要先new對象,但是在開發中這樣做對話會增加程序之間的耦合度,使得程序的維護成本直線上升。
所以有了IoC容器我們可以將對象之間的依賴關係交由Spring進行控制,避免硬編碼所造成的過渡程序耦和。用戶也不必再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可以更專注於上層的應用。


com.user.factory下我們可以創建一個BeanFactory的類(com.user.daocom.user.service中的接口及其實現類這邊省略不談)。Bean在計算機英語中,有可重用組件的含義。而JavaBean就用java語言編寫的可重用組件。實際上javabean ≠ 實體類,很初學者認爲javabean就是實體類,但是實際上是javabean > 實體類。在我們的Spring項目中,它就是創建我們的servicedao對象的。在SpringIoC中,我們可以通過配置文件來進行對象之間依賴的管理,配置文件可以是xml也可以是properties,這邊我選擇的是properties。所以對於解耦的思路是:

  1. 需要一個配置文件來配置我們的servicedao,配置的內容:唯一標識=全限定類名(key=value
  2. 通過讀取配置文件中配置的內容,反射創建對象
public class BeanFactory {

    //定義一個Properties對象
    private static Properties props;

    //使用靜態代碼塊爲Properties對象賦值
    static {
        try {
            //實例化對象
            props = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            }
        }catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失敗!");
        }
    }

     /**
     * 根據Bean的名稱獲取bean對象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();  //用反射的方式創建對象,每次都會調用默認構造函數創建對象
        }catch(Exception e){
            e.printStackTrace();
        }
        return bean;
    }
}

下面展示的是bean.properties中的內容,該文件存放在resources文件夾中,有唯一標誌符和全限定類名組成。

accountService = com.itheima.service.impl.AccountServiceImpl
acoountDao = com.itheima.dao.impl.AccountDaoImpl

這樣做的話就可以減少程序之間的耦合(只能減少,不可能避免,new有的地方是必須的)。可以通過private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("acoountDao");來創建對象,避免使用private IAccountDao accountDao = new AccountDaoImpl();,即通過new來創建對象,從而減少了程序之間的耦合度。


但是這樣做存在的問題是bean = Class.forName(beanPath).newInstance();通過反射機制實例化對象時,每次都會調用默認構造函數創建對象,即每次訪問時都會創建一個對象,這樣就變成了多例對象,這並不是我們想要的。直觀的,我們通過在control層調用:

for(int i=0;i<5;i++){
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
        }

打印出來控制檯的結果爲,可以看到這事多例對象:

com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@511d50c0
com.user.service.impl.AccountServiceImpl@60e53b93
com.user.service.impl.AccountServiceImpl@5e2de80c
com.user.service.impl.AccountServiceImpl@1d44bcfa

簡要比較下單例和多例對象:

  • 多例對象:對象被創建多次,執行效率沒有單例對象高,不存在線程問題
  • 單例對象:只被創建一次,從而類中的成員也就只會初始化一次。但是存在線程問題

而我們在工程中想要的是單例對象。解決的思路就是創建一個容器來保存我們初識創建的對象,在之後的過程中每次使用到這個對象時可以直接到容器中去取,所用到的容器就是Map
下面展示修改過後的代碼:

public class BeanFactory {

    //定義一個Properties對象
    private static Properties props;

    //定義一個Map,用於存放我們要創建的對象。我們把它稱爲容器
    //這樣就能保證對象只被創建了一次,就是單例的了
    private static Map<String,Object> beans;

    //使用靜態代碼塊爲Properties對象賦值
    static {
        try {
            //實例化對象
            props = new Properties();
            //獲取properties文件的流對象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            //實例化容器
            beans = new HashMap<String, Object>();
            //取出配置文件中所有的key
            Enumeration keys = props.keys();
            //遍歷枚舉
            while(keys.hasMoreElements()){
                //取出每個key
                String key = keys.nextElement().toString();
                //根據key獲取value
                String beanPath = props.getProperty(key);
                //反射創建對象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器之中
                beans.put(key,value);
            }
        }catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失敗!");
        }
    }

    /**
     * 根據bean的名稱獲取對象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}

這樣,我們就構造出來單例對象,控制檯打印信息如下所示:

com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6
com.user.service.impl.AccountServiceImpl@610455d6

補充:

  1. servlet是單例的
  2. 我們在service和dao的開發中,一般不定義成員變量,而是將變量定義到成員方法中對其進行操控修改
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章