Spring筆記3 工廠模式

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 : 賬戶的持久層實現類

其中,有兩個依賴關係

  1. 表現層調用業務層時
public class Client {
    public static void main(String[] args) {
        IAccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}
  1. 業務層調用持久層時
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,

  1. 需要一個配置文件來配置service和dao
    內容:唯一標識=全限定類名(key=value)
    配置文件可以是xml也可以是properties

  2. 讀取配置文件中配置的內容,通過反射創建對象

工廠模式解耦

在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;
    }
}

注意:

  1. 讀配置文件時不要用FileInputStream,Web工程不好找路徑。用類加載器。
  2. 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
保存了賬戶
1

Process 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
保存了賬戶
5

Process finished with exit code 0

此時的AccountServiceImpl就是單例的了

但有一個問題是,這個i在多線程時是不安全的
應該把它移到方法裏面,就沒有這個問題了(實際使用一般也是這樣)

//    private int i = 1;

    public void  saveAccount(){
        int i = 1;
        accountDao.saveAccount();
        System.out.println(i);
        i++;
    }

工廠模式使用套路總結

  1. 創建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);
    }
}
  1. 配置文件
// beans.properties

accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
  1. 獲取Bean
// Client.java

public class Client {

    public static void main(String[] args) {
    	IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
    	as.saveAccount();
    }

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