1. 程式人生 > >創建對象與使用對象——談談工廠的作用

創建對象與使用對象——談談工廠的作用

方便 構圖 erb aof 常用 uda 行為 too jpg

工廠模式(包括簡單工廠模式、工廠方法模式和抽象工廠模式)到底有什麽用,很多時候通過反射機制就可以很靈活地創建對象,為毛還要工廠?技術分享,在本文中我將圍繞創建對象和使用對象來簡單談談工廠的作用。

與一個對象相關的職責通常有三類:對象本身所具有的職責、創建對象的職責和使用對象的職責。對象本身的職責比較容易理解,就是對象自身所具有的一些數據和行為,可通過一些公開的方法來實現它的職責。在本文中,我們將簡單討論一下對象的創建職責和使用職責。

在Java語言中,我們通常有以下幾種創建對象的方式:

(1) 使用new關鍵字直接創建對象;

(2) 通過反射機制創建對象;

(3) 通過clone()方法創建對象;

(4) 通過工廠類創建對象。

毫無疑問,在客戶端代碼中直接使用new關鍵字是最簡單的一種創建對象的方式,但是它的靈活性較差,下面通過一個簡單的示例來加以說明:

[java] view plain copy 技術分享技術分享
  1. class LoginAction {
  2. private UserDAO udao;
  3. public LoginAction() {
  4. udao = new JDBCUserDAO(); //創建對象
  5. }
  6. public void execute() {
  7. //其他代碼
  8. udao.findUserById(); //使用對象
  9. //其他代碼
  10. }
  11. }

在LoginAction類中定義了一個UserDAO類型的對象udao,在LoginAction的構造函數中創建了JDBCUserDAO類型的udao對象,並在execute()方法中調用了udao對象的findUserById()方法,這段代碼看上去並沒有什麽問題。下面我們來分析一下LoginAction和UserDAO之間的關系,LoginAction類負責創建了一個UserDAO子類的對象並使用UserDAO的方法來完成相應的業務處理,也就是說LoginAction即負責udao的創建又負責udao的使用,創建對象和使用對象的職責耦合在一起,這樣的設計會導致一個很嚴重的問題:如果在LoginAction中希望能夠使用UserDAO的另一個子類如HibernateUserDAO類型的對象,必須修改LoginAction類的源代碼,違反了“開閉原則”。如何解決該問題?

最常用的一種解決方法是將udao對象的創建職責從LoginAction類中移除,在LoginAction類之外創建對象,那麽誰來負責創建UserDAO對象呢?答案是:工廠類。通過引入工廠類,客戶類(如LoginAction)不涉及對象的創建,對象的創建者也不會涉及對象的使用。引入工廠類UserDAOFactory之後的結構如圖1所示:

技術分享

圖1 引入工廠類之後的結構圖

工廠類的引入將降低因為產品或工廠類改變所造成的維護工作量。如果UserDAO的某個子類的構造函數發生改變或者要需要添加或移除不同的子類,只要維護UserDAOFactory的代碼,而不會影響到LoginAction;如果UserDAO的接口發生改變,例如添加、移除方法或改變方法名,只需要修改LoginAction,不會給UserDAOFactory帶來任何影響。

在所有的工廠模式中,我們都強調一點:兩個類A和B之間的關系應該僅僅是A創建B或是A使用B,而不能兩種關系都有。將對象的創建和使用分離,也使得系統更加符合“單一職責原則”,有利於對功能的復用和系統的維護。

此外,將對象的創建和使用分離還有一個好處:防止用來實例化一個類的數據和代碼在多個類中到處都是,可以將有關創建的知識搬移到一個工廠類中,這在Joshua Kerievsky的《重構與模式》一書中有專門的一節來進行介紹。因為有時候我們創建一個對象不只是簡單調用其構造函數,還需要設置一些參數,可能還需要配置環境,如果將這些代碼散落在每一個創建對象的客戶類中,勢必會出現代碼重復、創建蔓延的問題,而這些客戶類其實無須承擔對象的創建工作,它們只需使用已創建好的對象就可以了。此時,可以引入工廠類來封裝對象的創建邏輯和客戶代碼的實例化/配置選項。

使用工廠類還有一個“不是特別明顯的”優點,一個類可能擁有多個構造函數,而在Java、C#等語言中構造函數名字都與類名相同,客戶端只能通過傳入不同的參數來調用不同的構造函數創建對象,從構造函數和參數列表中也許大家根本不了解不同構造函數所構造的產品的差異。但如果將對象的創建過程封裝在工廠類中,我們可以提供一系列名字完全不同的工廠方法,每一個工廠方法對應一個構造函數,客戶端可以以一種更加可讀、易懂的方式來創建對象,而且,從一組工廠方法中選擇一個意義明確的工廠方法,比從一組名稱相同參數不同的構造函數中選擇一個構造函數要方便很多。如圖2所示:

技術分享

在圖2中,矩形工廠類RectangleFactory提供了兩個工廠方法createRectangle()和createSquare(),一個用於創建長方形,一個用於創建正方形,這兩個方法比直接通過構造函數來創建長方形或正方形對象意義更加明確,也在一定程度上降低了客戶端調用時出錯的概率。

那麽,有人可能會問,是否需要為設計中的每一個類都配備一個工廠類?答案是:具體情況具體分析。如果產品類很簡單,而且不存在太多變數,其構造過程也很簡單,此時無須為其提供工廠類,直接在使用之前實例化即可,例如Java語言中的String類,我們就無須為它專門提供一個StringFactory,這樣做反而有點像殺雞用牛刀,大材小用,而且會導致工廠泛濫,增加系統的復雜度。

創建對象與使用對象——談談工廠的作用