https://www.bilibili.com/video/av47952931
p9~14
程序的耦合與解耦 (以jdbc註冊驅動爲例)
jdbc操作中,註冊數據庫驅動時,有2種方法
// 方法1
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 方法2
Class.forName("com.mysql.jdbc.Driver");
在pom.xml中添加了依賴的情況下都可以正常運行
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
但是,如果去掉這段依賴
方法1報Error,無法通過編譯
Error:(26, 56) java: 程序包com.mysql.jdbc不存在
而方法2報Exception,可以通過編譯(無法運行)
Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
因爲方法1依賴一個具體的驅動類,而方法2用反射,依賴的只是一個字符串
但是這個字符串仍是寫死在代碼裏的
應該寫到配置文件裏去,進一步減少耦合
實際開發中應該做到:
編譯期不依賴,運行時才依賴
解耦的思路:
第一步:使用反射來創建對象,而避免使用new關鍵字
第二步:通過讀取配置文件來獲取要創建的對象全限定類名
工廠模式
原始的分層實現方法
- Client : 模擬一個表現層,用於調用業務層
- IAccountService : 賬戶業務層的接口
- AccountServiceImpl : 賬戶的業務層實現類
- IAccountDao : 賬戶的持久層接口
- AccountDaoImpl : 賬戶的持久層實現類
其中,有兩個依賴關係
- 表現層調用業務層時
public class Client {
public static void main(String[] args) {
IAccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
- 業務層調用持久層時
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
都用到了new,耦合度高
如果此時把AccountDaoImpl的代碼刪了,Service就報錯了
和前面jdbc中編譯期的錯誤一樣
如何解除這種依賴?
Bean & BeanFactory
Bean —— 可重用組件
eg:一個Dao可能被多個Service使用,一個Service可能被多個Servlet使用,它們是可重用的
JavaBean —— 用java語言編寫的可重用組件
BeanFactory —— 創建Bean對象的工廠
eg:創建Dao和Service對象
要實現這個工廠,類似前面jdbc,
-
需要一個配置文件來配置service和dao
內容:唯一標識=全限定類名(key=value)
配置文件可以是xml也可以是properties -
讀取配置文件中配置的內容,通過反射創建對象
工廠模式解耦
在resources中新建beans.properties配置文件
(此處用properties因爲簡單,Spring中用的是xml)
accountService = com.itheima.service.impl.AccountServiceImpl
accountDao = com.itheima.dao.impl.AccountDaoImpl
創建BeanFactory類讀取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){
// 拋一個Error,沒有獲取配置信息後面想都不要想
throw new ExceptionInInitializerError("初始化properties失敗!");
}
}
// 根據Bean的名稱獲取bean對象
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;
}
}
注意:
- 讀配置文件時不要用FileInputStream,Web工程不好找路徑。用類加載器。
- getBean()返回的是Object類型
把兩處使用new創建對象的改爲用反射創建
Client中
// IAccountService as = new AccountServiceImpl();
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
AccountServiceImpl中
// private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
Object類強轉爲對應的類
改進後程序的UML類圖長這樣
此時如果把AccountServiceImpl刪了,程序可以運行,拋ClassNotFoundException
工廠模式的問題與改進
如果要在Client中多次調用Service?
for(int i=0;i<5;i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
並在AccountServiceImpl中加一個成員變量i
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
private int i = 1;
public void saveAccount(){
accountDao.saveAccount();
System.out.println(i);
i++;
}
}
運行
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
1
com.itheima.service.impl.AccountServiceImpl@511d50c0
保存了賬戶
1
com.itheima.service.impl.AccountServiceImpl@60e53b93
保存了賬戶
1
com.itheima.service.impl.AccountServiceImpl@5e2de80c
保存了賬戶
1
com.itheima.service.impl.AccountServiceImpl@1d44bcfa
保存了賬戶
1Process finished with exit code 0
可以看到,AccountServiceImpl創建了5次,每次都是一個新的對象
此時的對象是多例,效率沒有單例高
可以在BeanFactory中把創建出的對象都存起來
//定義一個Map,用於存放我們要創建的對象。我們把它稱之爲容器
private static Map<String,Object> beans;
在靜態代碼塊中,得到配置文件的輸入流後,實例化這個容器。取出配置文件中所有的key-value,創建並保存它們
// 實例化容器
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);
}
獲取Beans,不用newInstance(),直接從容器中取即可
public static Object getBean(String beanName){
return beans.get(beanName);
}
這樣修改後,調用5次Service打印的結果是
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
1
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
2
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
3
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
4
com.itheima.service.impl.AccountServiceImpl@610455d6
保存了賬戶
5Process finished with exit code 0
此時的AccountServiceImpl就是單例的了
但有一個問題是,這個i在多線程時是不安全的
應該把它移到方法裏面,就沒有這個問題了(實際使用一般也是這樣)
// private int i = 1;
public void saveAccount(){
int i = 1;
accountDao.saveAccount();
System.out.println(i);
i++;
}
工廠模式使用套路總結
- 創建BeanFactory
// BeanFactory.java
public class BeanFactory {
//定義一個Properties對象
private static Properties props;
//定義一個Map,用於存放我們要創建的對象。稱之爲容器
private static Map<String,Object> beans;
//使用靜態代碼塊爲Properties對象賦值
static {
try {
// 1.實例化對象
props = new Properties();
// 2.獲取properties文件的流對象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
// 3.實例化容器
beans = new HashMap<String,Object>();
// 4.取出配置文件中所有的Key
Enumeration keys = props.keys();
// 5.遍歷枚舉
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的名稱獲取對象
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
- 配置文件
// beans.properties
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
- 獲取Bean
// Client.java
public class Client {
public static void main(String[] args) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
as.saveAccount();
}
}