在設計模式的教學和推廣過程當中,不少企業學員和在校學生常常問我,工廠模式(包括簡單工廠模式、工廠方法模式和抽象工廠模式)到底有什麼用,不少時候經過反射機制就能夠很靈活地建立對象,爲毛還要工廠?,在本文中我將圍繞建立對象和使用對象來簡單談談工廠的做用。java
與一個對象相關的職責一般有三類:對象自己所具備的職責、建立對象的職責和使用對象的職責。對象自己的職責比較容易理解,就是對象自身所具備的一些數據和行爲,可經過一些公開的方法來實現它的職責。在本文中,咱們將簡單討論一下對象的建立職責和使用職責。設計模式
在Java語言中,咱們一般有如下幾種建立對象的方式:app
(1) 使用new關鍵字直接建立對象;函數
(2) 經過反射機制建立對象;oop
(3) 經過clone()方法建立對象;this
(4) 經過工廠類建立對象。spa
毫無疑問,在客戶端代碼中直接使用new關鍵字是最簡單的一種建立對象的方式,可是它的靈活性較差,下面經過一個簡單的示例來加以說明: .net
在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所示:
那麼,有人可能會問,是否須要爲設計中的每個類都配備一個工廠類?答案是:具體狀況具體分析。若是產品類很簡單,並且不存在太多變數,其構造過程也很簡單,此時無須爲其提供工廠類,直接在使用以前實例化便可,例如Java語言中的String類,咱們就無須爲它專門提供一個StringFactory,這樣作反而有點像殺雞用牛刀,大材小用,並且會致使工廠氾濫,增長系統的複雜度。