1 場景問題#
大家都知道伤极,在Java應(yīng)用開發(fā)中蛹找,要“面向接口編程”。那么什么是接口塑荒?接口有什么作用熄赡?接口如何使用?一起來回顧一下齿税。
1.1 接口回顧##
- Java中接口的概念
在Java中接口是一種特殊的抽象類彼硫,跟一般的抽象類相比,接口里面的所有方法都是抽象方法凌箕,接口里面的所有屬性都是常量拧篮。也就是說,接口里面是只有方法定義而不會有任何方法實現(xiàn)牵舱。
- 接口用來干什么
通常用接口來定義實現(xiàn)類的外觀串绩,也就是實現(xiàn)類的行為定義,用來約束實現(xiàn)類的行為芜壁。接口就相當(dāng)于一份契約礁凡,根據(jù)外部應(yīng)用需要的功能,約定了實現(xiàn)類應(yīng)該要實現(xiàn)的功能慧妄,但是具體的實現(xiàn)類除了實現(xiàn)接口約定的功能外顷牌,還可以根據(jù)需要實現(xiàn)一些其它的功能,這是允許的塞淹,也就是說實現(xiàn)類的功能包含但不僅限于接口約束的功能窟蓝。
通過使用接口,可以實現(xiàn)不相關(guān)類的相同行為饱普,而不需考慮這些類之間的層次關(guān)系运挫,接口就是實現(xiàn)類對外的外觀。
- 接口的思想
根據(jù)接口的作用和用途套耕,濃縮下來谁帕,接口的思想就是“封裝隔離”。
通常提到封裝是指對數(shù)據(jù)的封裝箍铲,但是這里的封裝是指“對被隔離體的行為的封裝”雇卷,或者是“對被隔離體的職責(zé)的封裝”;而隔離指的是外部調(diào)用和內(nèi)部實現(xiàn)颠猴,外部調(diào)用只能通過接口進行調(diào)用关划,而外部調(diào)用是不知道內(nèi)部具體實現(xiàn)的,也就是說外部調(diào)用和內(nèi)部實現(xiàn)是被接口隔離開的翘瓮。
- 接口的好處
由于外部調(diào)用和內(nèi)部實現(xiàn)被接口隔離開了贮折,那么只要接口不變,內(nèi)部實現(xiàn)的變化就不會影響到外部應(yīng)用资盅,從而使得系統(tǒng)更靈活调榄,具有更好的擴展性和可維護性踊赠,這也就是所謂“接口是系統(tǒng)可插拔性的保證”這句話的意思。
- 接口和抽象類的選擇
既然接口是一種特殊的抽象類每庆,那么在開發(fā)中筐带,何時選用接口,何時選用抽象類呢缤灵?對于它們的選擇伦籍,在開發(fā)中是一個很重要的問題,特別總結(jié)兩句話給大家:優(yōu)先選用接口腮出。在如下情況應(yīng)選擇抽象類:既要定義子類的行為帖鸦,又要為子類提供公共的功能。
1.2 面向接口編程##
面向接口編程是Java編程中的一個重要原則胚嘲。
在Java 程序設(shè)計里面作儿,非常講究層的劃分和模塊的劃分。通常按照三層來劃分Java程序馋劈,分別是表現(xiàn)層攻锰、邏輯層、數(shù)據(jù)層妓雾,它們之間都要通過接口來通訊口注。
在每一個層里面,又有很多個小模塊君珠,一個小模塊對外也應(yīng)該是一個整體,那么一個模塊對外也應(yīng)該提供接口娇斑,其它地方需要使用到這個模塊的功能策添,都應(yīng)該通過此接口來進行調(diào)用。這也就是常說的“接口是被其隔離部分的外觀”毫缆∥ㄖ瘢基本的三層結(jié)構(gòu)如圖1所示:
在一個層內(nèi)部的各個模塊交互也要通過接口,如圖所示:
上面頻頻提到“組件”苦丁,那么什么是組件呢浸颓?先簡單的名詞解釋一下:
所謂組件:從設(shè)計上講,組件就是能完成一定功能的封裝體旺拉。小到一個類产上,大到一個系統(tǒng),都可以稱為組件蛾狗,因為一個小系統(tǒng)放到更大的系統(tǒng)里面去晋涣,也就當(dāng)個組件而已。事實上沉桌,從設(shè)計的角度看谢鹊,系統(tǒng)算吩、子系統(tǒng)、模塊佃扼、組件等說的其實是同一回事情偎巢,都是完成一定功能的封裝體,只不過功能多少不同而已兼耀。
繼續(xù)剛才的思路压昼,大家會發(fā)現(xiàn),不管是一層還是一個模塊或者一個組件翠订,都是一個被接口隔離的整體巢音,那么下面我們就不去區(qū)分它們,統(tǒng)一認為都是接口隔離體即可尽超,如圖所示:
既然在Java中需要面向接口編程官撼,那么在程序中到底如何使用接口,來做到真正的面向接口編程呢似谁?
1.3 不用模式的解決方案##
回憶一下傲绣,以前是如何使用接口的呢,假設(shè)有一個接口叫Api巩踏,然后有一個實現(xiàn)類Impl實現(xiàn)了它秃诵,在客戶端怎么用這個接口呢?
通常都是在客戶端創(chuàng)建一個Impl的實例塞琼,把它賦值給一個Api接口類型的變量菠净,然后客戶端就可以通過這個變量來操作接口的功能了,此時具體的結(jié)構(gòu)圖如圖:
- 先定義接口Api彪杉,示例代碼如下:
/**
* 某個接口(通用的毅往、抽象的、非具體的功能)
*/
public interface Api {
/**
* 某個具體的功能方法的定義派近,用test1來演示一下攀唯。
* 這里的功能很簡單,把傳入的s打印輸出即可
* @param s 任意想要打印輸出的字符串
*/
public void test1(String s);
}
- 既然有了接口渴丸,自然就要有實現(xiàn)侯嘀,定義實現(xiàn)Impl,示例代碼如下:
/**
* 對接口的實現(xiàn)
*/
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
- 那么此時的客戶端怎么寫呢谱轨?按照Java的知識戒幔,接口不能直接使用,需要使用接口的實現(xiàn)類碟嘴,示例代碼如下:
/**
* 客戶端:測試使用Api接口
*/
public class Client {
public static void main(String[] args) {
Api api = new Impl();
api.test1("哈哈溪食,不要緊張,只是個測試而已娜扇!");
}
}
1.4 有何問題##
上面寫得沒錯吧错沃,在Java的基礎(chǔ)知識里面就是這么學(xué)的栅组,難道這有什么問題嗎?請仔細看位于客戶端的下面這句話:
Api api = new Impl();
然后再想想接口的功能和思想枢析,發(fā)現(xiàn)什么了玉掸?仔細再想想?
你會發(fā)現(xiàn)在客戶端調(diào)用的時候醒叁,客戶端不但知道了接口司浪,同時還知道了具體的實現(xiàn)就是Impl。而接口的思想是“封裝隔離”把沼,而Impl這個實現(xiàn)類啊易,應(yīng)該是被接口Api封裝并同客戶端隔離開的,也就是說饮睬,客戶端根本就不應(yīng)該知道具體的實現(xiàn)類是Impl租谈。
有朋友說,那好捆愁,我就把Impl從客戶端拿掉割去,讓Api真正的對實現(xiàn)進行“封裝隔離”,然后我們還是面向接口來編程昼丑∩肽妫可是,新的問題出現(xiàn)了菩帝,當(dāng)他把“new Impl()”去掉過后咖城,發(fā)現(xiàn)他無法得到Api接口對象了,怎么辦呢呼奢?
把這個問題描述一下:
在Java編程中酒繁,出現(xiàn)只知接口而不知實現(xiàn),該怎么辦控妻?
就像現(xiàn)在的Client,它知道要使用Api接口揭绑,但是不知由誰實現(xiàn)弓候,也不知道如何實現(xiàn),從而得不到接口對象他匪,就無法使用接口菇存,該怎么辦呢?
2 解決方案#
2.1 簡單工廠來解決問題##
用來解決上述問題的一個合理的解決方案就是簡單工廠邦蜜,那么什么是簡單工廠呢依鸥?
- 簡單工廠定義
- 應(yīng)用簡單工廠來解決的思路
分析上面的問題,雖然不能讓模塊外部知道模塊內(nèi)的具體實現(xiàn)悼沈,但是模塊內(nèi)部是可以知道實現(xiàn)類的贱迟,而且創(chuàng)建接口是需要具體實現(xiàn)類的姐扮。
那么干脆在模塊內(nèi)部新建一個類,在這個類里面來創(chuàng)建接口衣吠,然后把創(chuàng)建好的接口返回給客戶端茶敏,這樣外部應(yīng)用就只需要根據(jù)這個類來獲取相應(yīng)的接口對象,然后就可以操作接口定義的方法了缚俏。把這樣的對象稱為簡單工廠惊搏,就叫Factory吧。
這樣一來忧换,客戶端就可以通過這個Factory來獲取需要的接口對象恬惯,然后調(diào)用接口的方法來實現(xiàn)需要的功能,而且客戶端也不用再關(guān)心具體實現(xiàn)了亚茬。
2.2 簡單工廠結(jié)構(gòu)和說明##
簡單工廠的結(jié)構(gòu)如圖所示:
Api:定義客戶所需要的功能接口
Impl:具體實現(xiàn)Api的實現(xiàn)類酪耳,可能會有多個
Factory:工廠,選擇合適的實現(xiàn)類來創(chuàng)建Api接口對象
Client:客戶端才写,通過Factory去獲取Api接口對象葡兑,然后面向Api接口編程
2.3 簡單工廠示例代碼##
- 先看看Api的定義,示例代碼如下:
/**
* 接口的定義赞草,該接口可以通過簡單工廠來創(chuàng)建
*/
public interface Api {
/**
* 示意讹堤,具體的功能方法的定義
* @param s 示意,需要的參數(shù)
*/
public void operation(String s);
}
- 定義了接口厨疙,該來實現(xiàn)它了洲守,ImplA的示例代碼如下:
/**
* 接口的具體實現(xiàn)對象A
*/
public class ImplA implements Api{
public void operation(String s) {
//實現(xiàn)功能的代碼,示意一下
System.out.println("ImplA s=="+s);
}
}
/**
* 接口的具體實現(xiàn)對象B
*/
public class ImplB implements Api{
public void operation(String s) {
// 實現(xiàn)功能的代碼沾凄,示意一下
System.out.println("ImplB s=="+s);
}
}
- 該來看看簡單工廠的實現(xiàn)梗醇,示例代碼如下:
/**
* 工廠類,用來創(chuàng)造Api對象
*/
public class Factory {
/**
* 具體的創(chuàng)造Api對象的方法
* @param condition 示意撒蟀,從外部傳入的選擇條件
* @return 創(chuàng)造好的Api對象
*/
public static Api createApi(int condition){
//應(yīng)該根據(jù)某些條件去選擇究竟創(chuàng)建哪一個具體的實現(xiàn)對象叙谨,
//這些條件可以從外部傳入,也可以從其它途徑獲取保屯。
//如果只有一個實現(xiàn)手负,可以省略條件,因為沒有選擇的必要姑尺。
//示意使用條件
Api api = null;
if(condition == 1) {
api = new ImplA();
} else if(condition == 2) {
api = new ImplB();
}
return api;
}
}
- 再來看看客戶端的示意竟终,示例代碼如下:
/**
* 客戶端,使用Api接口
*/
public class Client {
public static void main(String[] args) {
//通過簡單工廠來獲取接口對象
Api api = Factory.createApi(1);
api.operation("正在使用簡單工廠");
}
}
2.4 使用簡單工廠重寫示例##
要使用簡單工廠來重寫前面的示例切蟋,主要就是要創(chuàng)建一個簡單工廠對象统捶,讓簡單工廠來負責(zé)創(chuàng)建接口對象。然后讓客戶端通過工廠來獲取接口對象,而不再由客戶端自己去創(chuàng)建接口的對象了喘鸟。此時系統(tǒng)的結(jié)構(gòu)如圖所示:
接口Api和實現(xiàn)類Impl都和前面的示例一樣匆绣,就不去贅述了。
新創(chuàng)建一個簡單工廠的對象迷守,示例代碼如下:
/**
* 工廠類犬绒,用來創(chuàng)造Api對象
*/
public class Factory {
/**
* 具體的創(chuàng)造Api對象的方法
* @return 創(chuàng)造好的Api對象
*/
public static Api createApi(){
//由于只有一個實現(xiàn),就不用條件判斷了
return new Impl();
}
}
- 客戶端如何使用簡單工廠提供的功能呢兑凿?這個時候凯力,客戶端就不用再自己去創(chuàng)建接口的對象了,應(yīng)該使用工廠來獲取礼华,經(jīng)過改造咐鹤,客戶端代碼如下:
/**
* 客戶端:測試使用Api接口
*/
public class Client {
public static void main(String[] args) {
//重要改變,沒有new Impl()了圣絮,取而代之Factory.createApi()
Api api = Factory.createApi();
api.test1("哈哈祈惶,不要緊張,只是個測試而已扮匠!");
}
}
就如同上面的示例捧请,客戶端通過簡單工廠創(chuàng)建了一個實現(xiàn)接口的對象,然后面向接口編程棒搜,從客戶端來看疹蛉,它根本就不知道具體的實現(xiàn)是什么,也不知道是如何實現(xiàn)的力麸,它只知道通過工廠獲得了一個接口對象可款,然后就能通過這個接口來獲取想要的功能。
事實上克蚂,簡單工廠能幫助我們真正開始面向接口編程闺鲸,像以前的做法,其實只是用到了接口的多態(tài)那部分的功能埃叭,最重要的“封裝隔離性”并沒有體現(xiàn)出來摸恍。
3 模式講解#
3.1 典型疑問##
首先來解決一個常見的疑問:可能有朋友會認為,上面示例中的簡單工廠看起來不就是把客戶端里面的“new Impl()”移動到簡單工廠里面嗎赤屋?不還是一樣通過new一個實現(xiàn)類來得到接口嗎误墓?把“new Impl()”這句話放到客戶端和放到簡單工廠里面有什么不同嗎?
理解這個問題的重點就在于理解簡單工廠所處的位置益缎。
根據(jù)前面的學(xué)習(xí),我們知道接口是用來封裝隔離具體的實現(xiàn)的然想,目標(biāo)就是不要讓客戶端知道封裝體內(nèi)部的具體實現(xiàn)莺奔。簡單工廠的位置是位于封裝體內(nèi)的,也就是簡單工廠是跟接口和具體的實現(xiàn)在一起的,算是封裝體內(nèi)部的一個類令哟,所以簡單工廠知道具體的實現(xiàn)類是沒有關(guān)系的恼琼。整理一下簡單工廠的結(jié)構(gòu)圖,新的圖如圖所示:
圖中虛線框屏富,就好比是一個組件的包裝邊界晴竞,表示接口、實現(xiàn)類和工廠類組合成了一個組件狠半,在這個封裝體里面噩死,只有接口和工廠是對外的,也就是讓外部知道并使用的神年,所以故意漏了一些在虛線框外已维,而具體的實現(xiàn)類是不對外的,被完全包含在虛線框內(nèi)已日。
對于客戶端而言垛耳,只是知道了接口Api和簡單工廠Factory,通過Factory就可以獲得Api了飘千,這樣就達到了讓Client在不知道具體實現(xiàn)類的情況下獲取接口Api堂鲜。所以看似簡單的把“new Impl()”這句話從客戶端里面移動到了簡單工廠里面,其實是有了質(zhì)的變化的护奈。
3.2 認識簡單工廠##
- 簡單工廠的功能
工廠嘛缔莲,就是用來造東西的。在Java里面逆济,通常情況下是用來造接口的酌予,但是也可以造抽象類,甚至是一個具體的類實例奖慌。一定要注意抛虫,雖然前面的示例是利用簡單工廠來創(chuàng)建的接口,但是也是可以用簡單工廠來創(chuàng)建抽象類或者是普通類的實例的简僧。
- 靜態(tài)工廠
使用簡單工廠的時候建椰,通常不用創(chuàng)建簡單工廠類的類實例,沒有創(chuàng)建實例的必要岛马。因此可以把簡單工廠類實現(xiàn)成一個工具類棉姐,直接使用靜態(tài)方法就可以了,也就是說簡單工廠的方法通常都是靜態(tài)的啦逆,所以也被稱為靜態(tài)工廠伞矩。如果要防止客戶端無謂的創(chuàng)造簡單工廠實例,還可以把簡單工廠的構(gòu)造方法私有化了夏志。
- 萬能工廠
一個簡單工廠可以包含很多用來構(gòu)造東西的方法乃坤,這些方法可以創(chuàng)造不同的接口、抽象類或者是類實例,一個簡單工廠理論上可以構(gòu)造任何東西湿诊,所以又稱之為“萬能工廠”狱杰。
雖然上面的實例中,在簡單工廠里面只有一個方法厅须,但事實上仿畸,是可以有很多這樣創(chuàng)建方法的,這點要注意朗和。
- 簡單工廠創(chuàng)建對象的范圍
雖然從理論上講错沽,簡單工廠什么都能造,但對于簡單工廠可創(chuàng)建對象的范圍例隆,通常不要太大甥捺,建議控制在一個獨立的組件級別或者一個模塊級別,也就是一個組件或模塊一個簡單工廠镀层。否則這個簡單工廠類會職責(zé)不明镰禾,有點大雜燴的感覺。
- 簡單工廠的調(diào)用順序示意圖
- 簡單工廠命名的建議
類名建議為“模塊名稱+Factory”唱逢,比如:用戶模塊的工廠就稱為:UserFactory
方法名稱通常為“get+接口名稱”或者是“create+接口名稱”吴侦,比如:有一個接口名稱為UserEbi,那么方法名稱通常為:getUserEbi 或者是 createUserEbi坞古。
當(dāng)然备韧,也有一些朋友習(xí)慣于把方法名稱命名為“new+接口名稱”州叠,比如:newUserEbi,我們不是很建議着绷。因為new在Java中代表特定的含義宏粤,而且通過簡單工廠的方法來獲取對象實例驶冒,并不一定每次都是要new一個新的實例。如果使用newUserEbi滞时,這會給人錯覺贼陶,好像每次都是new一個新的實例一樣饵沧。
3.3 簡單工廠中方法的寫法##
雖然說簡單工廠的方法多是用來造接口的吃粒,但是仔細分析就會發(fā)現(xiàn)潦俺,真正能實現(xiàn)功能的是具體的實現(xiàn)類,這些實現(xiàn)類是已經(jīng)做好的徐勃,并不是真的靠簡單工廠來創(chuàng)造出來的事示,簡單工廠的方法無外乎就是:實現(xiàn)了選擇一個合適的實現(xiàn)類來使用。
所以簡單工廠方法的內(nèi)部主要實現(xiàn)的功能是“選擇合適的實現(xiàn)類”來創(chuàng)建實例對象僻肖。既然要實現(xiàn)選擇肖爵,那么就需要選擇的條件或者是選擇的參數(shù),選擇條件或者是參數(shù)的來源通常又有幾種:
來源于客戶端臀脏,由Client來傳入?yún)?shù)
來源于配置文件劝堪,從配置文件獲取用于判斷的值
來源于程序運行期的某個值法挨,比如從緩存中獲取某個運行期的值
下面來看個示例,看看由客戶端來傳入?yún)?shù)幅聘,如何寫簡單工廠中的方法。
- 在剛才的示例上再添加一個實現(xiàn)窃植,稱為Impl2帝蒿,示例代碼如下:
/**
* 對接口的一種實現(xiàn)
*/
public class Impl2 implements Api{
public void test1(String s) {
System.out.println("Now In Impl The input s=="+s);
}
}
- 現(xiàn)在對Api這個接口,有了兩種實現(xiàn)巷怜,那么工廠類該怎么辦呢葛超?到底如何選擇呢?不可能兩個同時使用吧延塑,看看新的工廠類绣张,示例代碼如下:
/**
* 工廠類,用來創(chuàng)造Api的
*/
public class Factory {
/**
* 具體的創(chuàng)造Api的方法关带,根據(jù)客戶端的參數(shù)來創(chuàng)建接口
* @param type 客戶端傳入的選擇創(chuàng)造接口的條件
* @return 創(chuàng)造好的Api對象
*/
public static Api createApi(int type){
//這里的type也可以不由外部傳入侥涵,而是直接讀取配置文件來獲取
//為了把注意力放在模式本身上,這里就不去寫讀取配置文件的代碼了
//根據(jù)type來進行選擇宋雏,當(dāng)然這里的1和2應(yīng)該做成常量
Api api = null;
if(type==1){
api = new Impl();
}else if(type==2){
api = new Impl2();
}
return api;
}
}
- 客戶端沒有什么變化芜飘,只是在調(diào)用Factory的createApi方法的時候需要傳入?yún)?shù),示例代碼如下:
public class Client {
public static void main(String[] args) {
//注意這里傳遞的參數(shù)磨总,修改參數(shù)就可以修改行為嗦明,試試看吧
Api api = Factory.createApi(2);
api.test1("哈哈,不要緊張蚪燕,只是個測試而已娶牌!");
}
}
- 要注意這種方法有一個缺點
由于是從客戶端在調(diào)用工廠的時候,傳入選擇的參數(shù)馆纳,這就說明客戶端必須知道每個參數(shù)的含義诗良,也需要理解每個參數(shù)對應(yīng)的功能處理。這就要求必須在一定程度上厕诡,向客戶暴露一定的內(nèi)部實現(xiàn)細節(jié)累榜。
3.4 可配置的簡單工廠##
現(xiàn)在已經(jīng)學(xué)會通過簡單工廠來選擇具體的實現(xiàn)類了,可是還有問題灵嫌。比如:在現(xiàn)在的實現(xiàn)中壹罚,再新增加一種實現(xiàn),會怎樣呢寿羞?
那就需要修改工廠類猖凛,才能把新的實現(xiàn)添加到現(xiàn)有系統(tǒng)中。比如現(xiàn)在新加了一個實現(xiàn)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;
}
}
每次新增加一個實現(xiàn)類都來修改工廠類的實現(xiàn)辨泳,肯定不是一個好的實現(xiàn)方式虱岂。那么現(xiàn)在希望新增加了實現(xiàn)類過后不修改工廠類,該怎么辦呢菠红?
一個解決的方法就是使用配置文件第岖,當(dāng)有了新的實現(xiàn)類過后,只要在配置文件里面配置上新的實現(xiàn)類就好了试溯,在簡單工廠的方法里面可以使用反射蔑滓,當(dāng)然也可以使用IoC/DI(控制反轉(zhuǎn)/依賴注入,這個不在這里討論)來實現(xiàn)遇绞。
看看如何使用反射加上配置文件键袱,來實現(xiàn)添加新的實現(xiàn)類過后,無須修改代碼摹闽,就能把這個新的實現(xiàn)類加入應(yīng)用中蹄咖。
- 配置文件用最簡單的properties文件,實際開發(fā)中多是xml配置付鹿。定義一個名稱為“FactoryTest.properties”的配置文件澜汤,放置到Factory同一個包下面,內(nèi)容如下:
ImplClass=cn.javass.dp.simplefactory.example5.Impl
如果新添加了實現(xiàn)類倘屹,修改這里的配置就可以了银亲,就不需要修改程序了。
- 此時的工廠類實現(xiàn)如下:
/**
* 工廠類纽匙,用來創(chuàng)造Api對象
*/
public class Factory {
/**
* 具體的創(chuàng)造Api的方法务蝠,根據(jù)配置文件的參數(shù)來創(chuàng)建接口
* @return 創(chuàng)造好的Api對象
*/
public static Api createApi(){
//直接讀取配置文件來獲取需要創(chuàng)建實例的類
//至于如何讀取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();
}
}
//用反射去創(chuàng)建馏段,那些例外處理等完善的工作這里就不做了
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;
}
}
- 此時的客戶端就變得很簡單了,不再需要傳入?yún)?shù)践瓷,代碼示例如下:
public class Client {
public static void main(String[] args) {
Api api = Factory.createApi();
api.test1("哈哈院喜,不要緊張,只是個測試而已晕翠!");
}
}
3.5 簡單工廠的優(yōu)缺點##
幫助封裝:簡單工廠雖然很簡單喷舀,但是非常友好的幫助我們實現(xiàn)了組件的封裝,然后讓組件外部能真正面向接口編程淋肾。
解耦:通過簡單工廠硫麻,實現(xiàn)了客戶端和具體實現(xiàn)類的解耦。如同上面的例子樊卓,客戶端根本就不知道具體是由誰來實現(xiàn)拿愧,也不知道具體是如何實現(xiàn)的,客戶端只是通過工廠獲取它需要的接口對象碌尔。
可能增加客戶端的復(fù)雜度:如果通過客戶端的參數(shù)來選擇具體的實現(xiàn)類浇辜,那么就必須讓客戶端能理解各個參數(shù)所代表的具體功能和含義券敌,這會增加客戶端使用的難度,也部分暴露了內(nèi)部實現(xiàn)柳洋,這種情況可以選用可配置的方式來實現(xiàn)待诅。
不方便擴展子工廠:私有化簡單工廠的構(gòu)造方法,使用靜態(tài)方法來創(chuàng)建接口熊镣,也就不能通過寫簡單工廠類的子類來改變創(chuàng)建接口的方法的行為了咱士。不過,通常情況下是不需要為簡單工廠創(chuàng)建子類的轧钓。
3.6 思考簡單工廠##
- 簡單工廠的本質(zhì)
簡單工廠的本質(zhì)是:選擇實現(xiàn)。
注意簡單工廠的重點在選擇锐膜,實現(xiàn)是已經(jīng)做好了的毕箍。就算實現(xiàn)再簡單,也要由具體的實現(xiàn)類來實現(xiàn)道盏,而不是在簡單工廠里面來實現(xiàn)而柑。簡單工廠的目的在于為客戶端來選擇相應(yīng)的實現(xiàn),從而使得客戶端和實現(xiàn)之間解耦荷逞,這樣一來媒咳,具體實現(xiàn)發(fā)生了變化,就不用變動客戶端了种远,這個變化會被簡單工廠吸收和屏蔽掉涩澡。
實現(xiàn)簡單工廠的難點就在于“如何選擇”實現(xiàn),前面講到了幾種傳遞參數(shù)的方法坠敷,那都是靜態(tài)的參數(shù)妙同,還可以實現(xiàn)成為動態(tài)的參數(shù)。比如:在運行期間膝迎,由工廠去讀取某個內(nèi)存的值粥帚,或者是去讀取數(shù)據(jù)庫中的值,然后根據(jù)這個值來選擇具體的實現(xiàn)等等限次。
- 何時選用簡單工廠
建議在如下情況中芒涡,選用簡單工廠:
如果想要完全封裝隔離具體實現(xiàn),讓外部只能通過接口來操作封裝體卖漫,那么可以選用簡單工廠费尽,讓客戶端通過工廠來獲取相應(yīng)的接口,而無需關(guān)心具體實現(xiàn)懊亡;
如果想要把對外創(chuàng)建對象的職責(zé)集中管理和控制依啰,可以選用簡單工廠,一個簡單工廠可以創(chuàng)建很多的店枣、不相關(guān)的對象速警,可以把對外創(chuàng)建對象的職責(zé)集中到一個簡單工廠來叹誉,從而實現(xiàn)集中管理和控制。
3.7 相關(guān)模式##
- 簡單工廠和抽象工廠模式
簡單工廠是用來選擇實現(xiàn)的闷旧,可以選擇任意接口的實現(xiàn)长豁,一個簡單工廠可以有多個用于選擇并創(chuàng)建對象的方法,多個方法創(chuàng)建的對象可以有關(guān)系也可以沒有關(guān)系忙灼。
抽象工廠模式是用來選擇產(chǎn)品簇的實現(xiàn)的匠襟,也就是說一般抽象工廠里面有多個用于選擇并創(chuàng)建對象的方法,但是這些方法所創(chuàng)建的對象之間通常是有關(guān)系的该园,這些被創(chuàng)建的對象通常是構(gòu)成一個產(chǎn)品簇所需要的部件對象酸舍。
所以從某種意義上來說,簡單工廠和抽象工廠是類似的里初,如果抽象工廠退化成為只有一個實現(xiàn)啃勉,不分層次,那么就相當(dāng)于簡單工廠了双妨。
-
簡單工廠和工廠方法模式
簡單工廠和工廠方法模式也是非常類似的淮阐。工廠方法的本質(zhì)也是用來選擇實現(xiàn)的,跟簡單工廠的區(qū)別在于工廠方法是把選擇具體實現(xiàn)的功能延遲到子類去實現(xiàn)刁品。如果把工廠方法中選擇的實現(xiàn)放到父類直接實現(xiàn)泣特,那就等同于簡單工廠。
簡單工廠和能創(chuàng)建對象實例的模式
簡單工廠的本質(zhì)是選擇實現(xiàn)挑随,所以它可以跟其它任何能夠具體的創(chuàng)建對象實例的模式配合使用状您,比如:單例模式竞阐、原型模式、生成器模式等等骆莹。