研磨設計模式之簡單工廠模式-3

3  模式講解

3.1  典型疑問

        首先來解決一個常見的疑問:可能有朋友會認爲,上面示例中的簡單工廠看起來不就是把客戶端裏面的「new Impl()」移動到簡單工廠裏面嗎?不仍是同樣經過new一個實現類來獲得接口嗎?把「new Impl()」這句話放到客戶端和放到簡單工廠裏面有什麼不一樣嗎?
        理解這個問題的重點就在於理解簡單工廠所處的位置。
        根據前面的學習,咱們知道接口是用來封裝隔離具體的實現的,目標就是不要讓客戶端知道封裝體內部的具體實現。簡單工廠的位置是位於封裝體內的,也就是簡單工廠是跟接口和具體的實如今一塊兒的,算是封裝體內部的一個類,因此簡單工廠知道具體的實現類是沒有關係的。整理一下簡單工廠的結構圖,新的圖如圖7所示:


                                        圖7  整理後的簡單工廠結構
       html

        圖7中虛線框,就比如是一個組件的包裝邊界,表示接口、實現類和工廠類組合成了一個組件,在這個封裝體裏面,只有接口和工廠是對外的,也就是讓外部知道並使用的,因此故意漏了一些在虛線框外,而具體的實現類是不對外的,被徹底包含在虛線框內。
        對於客戶端而言,只是知道了接口Api和簡單工廠Factory,經過Factory就能夠得到Api了,這樣就達到了讓Client在不知道具體實現類的狀況下獲取接口Api。
        因此看似簡單的把「new Impl()」這句話從客戶端裏面移動到了簡單工廠裏面,實際上是有了質的變化的。java

3.2  認識簡單工廠

(1)簡單工廠的功能
        工廠嘛,就是用來造東西的。在Java裏面,一般狀況下是用來造接口的,可是也能夠造抽象類,甚至是一個具體的類實例。
         必定要注意,雖然前面的示例是利用簡單工廠來建立的接口,可是也是能夠用簡單工廠來建立抽象類或者是普通類的實例的。
(2)靜態工廠
        使用簡單工廠的時候,一般不用建立簡單工廠類的類實例,沒有建立實例的必要。所以能夠把簡單工廠類實現成一個工具類,直接使用靜態方法就能夠了,也就是說簡單工廠的方法一般都是靜態的,因此也被稱爲靜態工廠。若是要防止客戶端無謂的創造簡單工廠實例,還能夠把簡單工廠的構造方法私有化了。
(3)萬能工廠
        一個簡單工廠能夠包含不少用來構造東西的方法,這些方法能夠創造不一樣的接口、抽象類或者是類實例,一個簡單工廠理論上能夠構造任何東西,因此又稱之爲「萬能工廠」。
       雖然上面的實例中,在簡單工廠裏面只有一個方法,但事實上,是能夠有不少這樣建立方法的,這點要注意。
(4)簡單工廠建立對象的範圍
       雖然從理論上講,簡單工廠什麼都能造,但對於簡單工廠可建立對象的範圍,一般不要太大,建議控制在一個獨立的組件級別或者一個模塊級別,也就是一個組件或模塊一個簡單工廠。不然這個簡單工廠類會職責不明,有點大雜燴的感受。
(5)簡單工廠的調用順序示意圖
       簡單工廠的調用順序如圖8所示:


 圖8  簡單工廠的調用順序示意圖數據庫

(6)簡單工廠命名的建議編程

 

  • 類名建議爲「模塊名稱+Factory」,好比:用戶模塊的工廠就稱爲:UserFactory
  • 方法名稱一般爲「get+接口名稱」或者是「create+接口名稱」,好比:有一個接口名稱爲UserEbi,那麼方法名稱一般爲:getUserEbi 或者是 createUserEbi。
  • 固然,也有一些朋友習慣於把方法名稱命名爲「new+接口名稱」,好比:newUserEbi,咱們不是很建議。由於new在Java中表明特定的含義,並且經過簡單工廠的方法來獲取對象實例,並不必定每次都是要new一個新的實例。若是使用newUserEbi,這會給人錯覺,好像每次都是new一個新的實例同樣。

 

3.3  簡單工廠中方法的寫法

 

        雖說簡單工廠的方法可能是用來造接口的,可是仔細分析就會發現,真正能實現功能的是具體的實現類,這些實現類是已經作好的,並非真的靠簡單工廠來創造出來的,簡單工廠的方法無外乎就是:實現了選擇一個合適的實現類來使用。
        因此簡單工廠方法的內部主要實現的功能是「選擇合適的實現類」來建立實例對象。既然要實現選擇,那麼就須要選擇的條件或者是選擇的參數,選擇條件或者是參數的來源一般又有幾種:設計模式

  • 來源於客戶端,由Client來傳入參數
  • 來源於配置文件,從配置文件獲取用於判斷的值
  • 來源於程序運行期的某個值,好比從緩存中獲取某個運行期的值

         下面來看個示例,看看由客戶端來傳入參數,如何寫簡單工廠中的方法。
(1)在剛纔的示例上再添加一個實現,稱爲Impl2,示例代碼以下:api

/**
 * 對接口的一種實現 
 */
public class Impl2 implements Api{
	public void test1(String s) {
		System.out.println("Now In Impl The input s=="+s);
	}
}

(2)如今對Api這個接口,有了兩種實現,那麼工廠類該怎麼辦呢?到底如何選擇呢?不可能兩個同時使用吧,看看新的工廠類,示例代碼以下:緩存

/**
 * 工廠類,用來創造Api的
 */
public class Factory {
	/**
	 * 具體的創造Api的方法,根據客戶端的參數來建立接口
	 * @param type 客戶端傳入的選擇創造接口的條件
	 * @return 創造好的Api對象
	 */
	public static Api createApi(int type){
		//這裏的type也能夠不禁外部傳入,而是直接讀取配置文件來獲取
		//爲了把注意力放在模式自己上,這裏就不去寫讀取配置文件的代碼了
	
		//根據type來進行選擇,固然這裏的1和2應該作成常量
		Api api = null;
		if(type==1){
			api = new Impl();
		}else if(type==2){
			api = new Impl2();
		}
		return api;
	}
}

(3)客戶端沒有什麼變化,只是在調用Factory的createApi方法的時候須要傳入參數,示例代碼以下:工具

public class Client {
	public static void main(String[] args) {
		//注意這裏傳遞的參數,修改參數就能夠修改行爲,試試看吧
		Api api = Factory.createApi(2);
		api.test1("哈哈,沒關係張,只是個測試而已!");
	}
}

(4)要注意這種方法有一個缺點
因爲是從客戶端在調用工廠的時候,傳入選擇的參數,這就說明客戶端必須知道每一個參數的含義,也須要理解每一個參數對應的功能處理。這就要求必須在必定程度上,向客戶暴露必定的內部實現細節。學習

3.4  可配置的簡單工廠

        如今已經學會經過簡單工廠來選擇具體的實現類了,但是還有問題。好比:在如今的實現中,再新增長一種實現,會怎樣呢?
        那就須要修改工廠類,才能把新的實現添加到現有系統中。好比如今新加了一個實現Impl3,那麼須要相似下面這樣來修改工廠類:測試

public class Factory {
	public static Api createApi(int type){
		Api api = null;
		if(type==1){
			api = new Impl();
		}else if(type==2){
			api = new Impl2();
		}

		else if(type==3){
			api = new Impl3();
		}
		return api;
	}
}

        每次新增長一個實現類都來修改工廠類的實現,確定不是一個好的實現方式。那麼如今但願新增長了實現類事後不修改工廠類,該怎麼辦呢?
         一個解決的方法就是使用配置文件,當有了新的實現類事後,只要在配置文件裏面配置上新的實現類就行了,在簡單工廠的方法裏面可使用反射,固然也可使用IoC/DI(控制反轉/依賴注入,這個不在這裏討論)來實現。
         看看如何使用反射加上配置文件,來實現添加新的實現類事後,無須修改代碼,就能把這個新的實現類加入應用中。
(1)配置文件用最簡單的properties文件,實際開發中可能是xml配置。定義一個名稱爲「FactoryTest.properties」的配置文件,放置到Factory同一個包下面,內容以下:

ImplClass=cn.javass.dp.simplefactory.example5.Impl

若是新添加了實現類,修改這裏的配置就能夠了,就不須要修改程序了。
(2)此時的工廠類實現以下:

/**
* 工廠類,用來創造Api對象
*/
public class Factory {
	/**
	 * 具體的創造Api的方法,根據配置文件的參數來建立接口
	 * @return 創造好的Api對象
	 */
	public static Api createApi(){
		//直接讀取配置文件來獲取須要建立實例的類
		//至於如何讀取Properties,還有如何反射這裏就不解釋了
		Properties p = new Properties(); 
		InputStream in = null;
		try {
			in = Factory.class.getResourceAsStream(
"FactoryTest.properties");
			p.load(in);
		} catch (IOException e) {
			System.out.println(
"裝載工廠配置文件出錯了,具體的堆棧信息以下:");
			e.printStackTrace();
		}finally{
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//用反射去建立,那些例外處理等完善的工做這裏就不作了
		Api api = null;
		try {
			api = (Api)Class.forName(p.getProperty("ImplClass"))
.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return api;
	}
}

(3)此時的客戶端就變得很簡單了,再也不須要傳入參數,代碼示例以下:

public class Client {
	public static void main(String[] args) {
		Api api = Factory.createApi();
		api.test1("哈哈,沒關係張,只是個測試而已!");
	}
}

把上面的示例代碼敲到電腦裏面,測試一下,體會體會。

3.5  簡單工廠的優缺點

  • 幫助封裝
        簡單工廠雖然很簡單,可是很是友好的幫助咱們實現了組件的封裝,而後讓組件外部能真正面向接口編程。
  • 解耦
        經過簡單工廠,實現了客戶端和具體實現類的解耦。
        如同上面的例子,客戶端根本就不知道具體是由誰來實現,也不知道具體是如何實現的,客戶端只是經過工廠獲取它須要的接口對象。
  • 可能增長客戶端的複雜度
        若是經過客戶端的參數來選擇具體的實現類,那麼就必須讓客戶端能理解各個參數所表明的具體功能和含義,這會增長客戶端使用的難度,也部分暴露了內部實現,這種狀況能夠選用可配置的方式來實現。
  • 不方便擴展子工廠
        私有化簡單工廠的構造方法,使用靜態方法來建立接口,也就不能經過寫簡單工廠類的子類來改變建立接口的方法的行爲了。不過,一般狀況下是不須要爲簡單工廠建立子類的。

 

3.6  思考簡單工廠

1:簡單工廠的本質
        簡單工廠的本質是:選擇實現
        注意簡單工廠的重點在選擇,實現是已經作好了的。就算實現再簡單,也要由具體的實現類來實現,而不是在簡單工廠裏面來實現。簡單工廠的目的在於爲客戶端來選擇相應的實現,從而使得客戶端和實現之間解耦,這樣一來,具體實現發生了變化,就不用變更客戶端了,這個變化會被簡單工廠吸取和屏蔽掉。
        實現簡單工廠的難點就在於 「如何選擇」實現,前面講到了幾種傳遞參數的方法,那都是靜態的參數,還能夠實現成爲動態的參數。好比:在運行期間,由工廠去讀取某個內存的值,或者是去讀取數據庫中的值,而後根據這個值來選擇具體的實現等等。
2:什麼時候選用簡單工廠
        建議在以下狀況中,選用簡單工廠:

  • 若是想要徹底封裝隔離具體實現,讓外部只能經過接口來操做封裝體,那麼能夠選用簡單工廠,讓客戶端經過工廠來獲取相應的接口,而無需關心具體實現
  • 若是想要把對外建立對象的職責集中管理和控制,能夠選用簡單工廠,一個簡單工廠能夠建立不少的、不相關的對象,能夠把對外建立對象的職責集中到一個簡單工廠來,從而實現集中管理和控制

3.7  相關模式

  • 簡單工廠和抽象工廠模式
        簡單工廠是用來選擇實現的,能夠選擇任意接口的實現,一個簡單工廠能夠有多個用於選擇並建立對象的方法,多個方法建立的對象能夠有關係也能夠沒有關係。
        抽象工廠模式是用來選擇產品簇的實現的,也就是說通常抽象工廠裏面有多個用於選擇並建立對象的方法,可是這些方法所建立的對象之間一般是有關係的,這些被建立的對象一般是構成一個產品簇所須要的部件對象。
        因此從某種意義上來講,簡單工廠和抽象工廠是相似的,若是抽象工廠退化成爲只有一個實現,不分層次,那麼就至關於簡單工廠了。
  • 簡單工廠和工廠方法模式
        簡單工廠和工廠方法模式也是很是相似的。
        工廠方法的本質也是用來選擇實現的,跟簡單工廠的區別在於工廠方法是把選擇具體實現的功能延遲到子類去實現。
        若是把工廠方法中選擇的實現放到父類直接實現,那就等同於簡單工廠。
  • 簡單工廠和能建立對象實例的模式
        簡單工廠的本質是選擇實現,因此它能夠跟其它任何可以具體的建立對象實例的模式配合使用,好比:單例模式、原型模式、生成器模式等等。

簡單工廠模式結束,謝謝觀賞


---------------------------------------------------------------------------

私塾在線學習網原創內容  跟着cc學設計系列 之 研磨設計模式

原創內容,轉載請註明出處【http://sishuok.com/forum/blogPost/list/117.html

---------------------------------------------------------------------------

相關文章
相關標籤/搜索