上篇博客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對象的時候會使用單例的形式,當我們在表現層循環獲取某個對象的時候,得到的將是同一個對象。