使用工廠模式解耦

上篇博客JDBC的demo展示了程序之間的耦合(程序間的依賴性),那麼如何進行解耦,就使用到工廠模式。

在開發過程中,項目主要分爲業務層、持久層(dao,管數據訪問的)和表現層。
Maven工程架構如下:

└── com
    └── eastnotes
        ├── dao
        │   ├── AccountDao.java
        │   └── impl
        │       └── AccountDaoImpl.java
        ├── service
        │   ├── AccountService.java
        │   └── impl
        │       └── AccountServiceImpl.java
        └── ui
            └── Client.java

dao層

/**
 * 賬戶的持久層操作類
 */
public class AccountDaoImpl implements AccountDao {

    public void saveAccount(){
        System.out.println("保存了賬戶");
    }

}
public interface IAccountDao {
 void saveAccount();
}

service層

service層負責業務邏輯,向dao層發送數據訪問指令。

/**
 * 賬戶的業務層實現類
 */
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDaoImpl();
    public void saveAccount(){
        accountDao.saveAccount();
    }
}
public interface IAccountService {
	//模擬保存賬戶
	void saveAccount();
}

ui是表現層,用於調用業務層方法。

/**
 * 模擬一個表現層,用於調業務層
 */
public class Client {
    public static void main(String[] args) {
        AccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}

最終得到dao層方法的輸出:

 保存了賬戶

Client類使用new關鍵字創建了一個業務層的實現類對象,在業務層實現類,AccountServiceImpl同樣使用new關鍵字創建了一個持久層實現

AccountService as = new AccountServiceImpl();
private AccountDao accountDao = new AccountDaoImpl();

那麼使用工廠模式解耦:
新建factory包,beanfactory類:用於解耦的工廠類。作用是創建Bean對象。
在這些組成部分中,有的類可以被重複使用,比如說service可以被很多的serverlet使用,dao可以被很多的service來使用。那麼這些被重複使用的類就可以成爲可重用組件,也就是Bean。所以,我們新創建的這個factory工廠類,就是來創建service和dao對象的。

解耦的思路分爲兩步:

  • 需要一個配置文件來配置我們的service和dao,配置文件的內容是一個個的鍵值對,鍵是唯一表示,值是這個類的全限定類名:唯一標誌=全限定類名。
  • 讀取配置文件,通過反射創建對象。配置文件可選xml或者是properties,在此處我們使用properties。

配置文件放在resources目錄下,取名爲bean.properties,文件內容如下:

accountService=com.eastnotes.service.impl.AccountServiceImpl
accountDao=com.eastnotes.dao.impl.AccountDaoImpl
├── java
│   └── com
│       └── eastnotes
│           ├── dao
│           │   ├── AccountDao.java
│           │   └── impl
│           │       └── AccountDaoImpl.java
│           ├── factory
│           │   └── BeanFactory.java
│           ├── service
│           │   ├── AccountService.java
│           │   └── impl
│           │       └── AccountServiceImpl.java
│           └── ui
│               └── Client.java
└── resources
    └── bean.properties
package com.eastnotes.factory;

import java.io.InputStream;
import java.util.Properties;

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對象
     * 使用static關鍵字可以通過類名來訪問這個函數
     * @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對象的工廠類之後,我們就可以改造業務層和表現層的對象創建方式了:
將業務層創建對象的方式改爲:

private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");

將表現層創建對象的方式改爲:

AccountService as = (AccountService) BeanFactory.getBean("accountService");

工廠解耦問題

我們將表現層的代碼改爲下面:

public class Client {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            AccountService as = (AccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
        }
    }
}

輸出結果:

com.itheima.service.impl.AccountServiceImpl@4f023edb
com.itheima.service.impl.AccountServiceImpl@3a71f4dd
com.itheima.service.impl.AccountServiceImpl@7adf9f5f
com.itheima.service.impl.AccountServiceImpl@85ede7b
com.itheima.service.impl.AccountServiceImpl@5674cd4d

由此我們可以看出,通過工廠類得到的對象是多例的,因爲每次生成的對象都不一樣。單例對象從始至終都是一個對象,我們的Servlet就是一個單例對象。單例對象的類成員只會被初始化一次,而多例對象會被初始化多次。

因此多例對象的執行效率沒有單例對象高。而單例對象存在線程安全問題。但是,在我們的Service和DAO中,我們並沒有在類中定義成員變量,因此不存在線程問題,所以我們並不需要多例對象。
出現多例對象的問題出現在工廠類中的下面這個代碼中:

bean = Class.forName(beanPath).newInstance(); 

我們通過反射使用newInstance創建對象的時候,每次都會調用默認構造函數。因此,當他創建完對象之後,我們應該馬上把對象存起來。用什麼存呢?Map容器

工廠解耦改進

首先,在工廠類中定義一個由static修飾的Map,用於存放我們要創建的對象,我們將它稱之爲容器。這個Map的key是String類型,value是Object類型。

然後,再靜態代碼塊中,取出配置文件中的所有key,然後根據每個key獲取value,也就是我們的全限定類名,然後通過反射創建每一個對象。此時key有了,value有了,我們就可以把它存入Map(容器)中。

靜態代碼塊只在類加載的時候執行一次,等到它執行完,這個容器內已經裝好了我們所有需要的Bean對象,那麼當我們獲取對象的時候就沒有這麼麻煩了。直接根據bean的名稱從容器中取出對象,然後直接返回就好了。

package com.eastnotes.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class BeanFactoryUpdate {
    //定義一個Properties對象
    private static Properties propres;

    //定義一個Map,用於存放我們要創建的對象,我們把它稱之爲容器,創建它是爲了實現單例對象
    public static Map<String,Object> beans;

    //使用靜態代碼塊爲Properties對象賦值,在類加載的時候只執行一次
    static {
        try {
            //實例化對象
            propres = new Properties();
            //獲取Properties文件的流對象
            InputStream in = BeanFactoryUpdate.class.getClassLoader().getResourceAsStream("bean.properties");
            propres.load(in);

            //實例化容器對象
            beans = new HashMap<String, Object>();

            //取出配置文件中所有的keys
            Enumeration keys = propres.keys();

            //遍歷枚舉
            while (keys.hasMoreElements()){
                //取出每個key
                String key = keys.nextElement().toString();
                //根據key獲取value
                String beanPath = propres.getProperty(key);
                //反射創建對象
                try {
                    Object value = Class.forName(beanPath).newInstance();
                    //把key和value存入map中
                    beans.put(key,value);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            throw new ExceptionInInitializerError("初始化Properties失敗");
        }
    }

    //【單例模式下】根據bean的名稱獲取bean對象
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }

}

這樣改進後,此工廠在創建Bean對象的時候會使用單例的形式,當我們在表現層循環獲取某個對象的時候,得到的將是同一個對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章