前言 工大有許多同學(xué)是做java的,大家都知道java最大的優(yōu)點(diǎn)是它的完全OO化和它在多年的發(fā)展過程中吸收和總結(jié)了許多先進(jìn)的框架與模式,其中工廠模式就是最常用的模式之一。下面我想將我在學(xué)習(xí)和實(shí)踐過程中對工廠模式的認(rèn)識與了解介紹給大家。由于筆者能力限制,在實(shí)踐中也沒參與過什么大的項(xiàng)目,筆者參與過的項(xiàng)目用到的工廠模式主要是簡單工廠模式(Simple Factory)和工廠方法模式(Factory Method),所以筆者在本文主要介紹的是這兩種模式。
準(zhǔn)備知識 在OO設(shè)計領(lǐng)域,我們知道前人總結(jié)了不少的經(jīng)驗(yàn),許多的經(jīng)驗(yàn)在現(xiàn)代軟件工程過程中已經(jīng)被認(rèn)為是原則來遵守。下面筆者摘抄幾項(xiàng)下文涉及到的OO原則的定義。
OCP(開閉原則,Open-Closed Principle):一個軟件的實(shí)體應(yīng)當(dāng)對擴(kuò)展開放,對修改關(guān)閉。我的理解是,對于一個已有的軟件,如果需要擴(kuò)展,應(yīng)當(dāng)在不需修改已有代碼的基礎(chǔ)上進(jìn)行。
DIP(依賴倒轉(zhuǎn)原則,Dependence Inversion Principle):要針對接口編程,不要針對實(shí)現(xiàn)編程。我的理解是,對于不同層次的編程,高層次暴露給低層次的應(yīng)當(dāng)只是接口,而不是它的具體類。
LoD(迪米特法則,Law of Demeter):只與你直接的朋友通信,而避免和陌生人通信。眾所周知類(或模塊)之間的通信越少,耦合度就越低,從而更有利于我們對軟件的宏觀管理。老子論“圣人之治”有相同的思想,《老子》云:“是以圣人之治,虛其心,實(shí)其腹,弱其志,常使民無知無欲。”,又云:“小國寡民,鄰國相望,雞犬之聲相聞,民至老死,不相往來。”。佩服我們的老祖宗,N千年前就想到了西方N千年后才想到的東西,同時也佩服《java與模式》的作者閻宏,可以用中國傳統(tǒng)哲學(xué)思想這么生動的說明這一軟件設(shè)計原則。
簡單工廠模式及實(shí)例 簡單工廠模式又叫靜態(tài)工廠模式,顧名思義,它是用來實(shí)例化目標(biāo)類的靜態(tài)類。下面我主要通過一個簡單的實(shí)例說明簡單工廠及其優(yōu)點(diǎn)。
比如有個國家的運(yùn)動員協(xié)會,他們是負(fù)責(zé)登記與注冊職業(yè)運(yùn)動員的(就好像我們國家的體育總局,呵呵,無論足球籃球還是乒乓球的運(yùn)動員都必須在這里注冊才能拿到我們國家職業(yè)運(yùn)動員牌照)。一家體育俱樂部(比如籃球的廣東宏遠(yuǎn),足球的深圳健力寶)想獲得球員為自己俱樂部效力,就必須通過這個運(yùn)動員協(xié)會。
根據(jù)DIP我們可以設(shè)計一個“運(yùn)動員”接口,“足球運(yùn)動員”和“籃球運(yùn)動員”(還有其他運(yùn)動員)都實(shí)現(xiàn)“運(yùn)動員”這個接口。而“運(yùn)動員協(xié)會”就是一個簡單工廠類,它負(fù)責(zé)實(shí)例化“運(yùn)動員”。我們這里的“俱樂部”就是一個客戶端(Client),不同的“俱樂部”就是不同的客戶端。具體如下圖表示:

對于不同的俱樂部對象(無論是八一還是深圳健力寶),他們都是面向“運(yùn)動員”接口編程,而不用管是“足球運(yùn)動員”還是“籃球運(yùn)動員”,也就是說實(shí)現(xiàn)了“運(yùn)動員”接口的具體類“足球運(yùn)動員”無需暴露給客戶端。這也滿足了DIP。但具體的俱樂部(比如足球的深圳健力寶)如何確保自己獲取的是自己想要的運(yùn)動員(健力寶俱樂部需要的當(dāng)然是足球運(yùn)動員)呢?這就需要“運(yùn)動員協(xié)會”這一工廠類了。俱樂部通過調(diào)用“運(yùn)動員協(xié)會”的具體方法,返回不同的實(shí)例。這同時也滿足了LoD,也就是“深圳健力寶足球俱樂部”對象不直接與“足球運(yùn)動員:李毅”對象通信,而是通過他們共同的“朋友”——“國家體育總局”通信。
下面給出各個類的程序,會有助于讀者更好的了解筆者之前的介紹。
| Code: |
[Copy to clipboard] |
運(yùn)動員.java public interface 運(yùn)動員 { public void 跑(); public void 跳(); }
足球運(yùn)動員.java public class 足球運(yùn)動員 implements 運(yùn)動員 {
public void 跑(){ //跑啊跑 } public void 跳(){ //跳啊跳 } }
籃球運(yùn)動員.java public class 籃球運(yùn)動員 implements 運(yùn)動員 {
public void 跑(){ //do nothing } public void 跳(){ //do nothing } }
體育協(xié)會.java public class 體育協(xié)會 { public static 運(yùn)動員 注冊足球運(yùn)動員(){ return new 足球運(yùn)動員(); } public static 運(yùn)動員 注冊籃球運(yùn)動員(){ return new 籃球運(yùn)動員(); }
}
俱樂部.java public class 俱樂部 { private 運(yùn)動員 守門員; private 運(yùn)動員 后衛(wèi); private 運(yùn)動員 前鋒;
public void test() { this.前鋒 = 體育協(xié)會.注冊足球運(yùn)動員(); this.后衛(wèi) = 體育協(xié)會.注冊足球運(yùn)動員(); this.守門員 = 體育協(xié)會.注冊足球運(yùn)動員(); 守門員.跑(); 后衛(wèi).跳(); } } | | 以上就是簡單工廠模式的一個簡單實(shí)例,讀者應(yīng)該想象不用接口不用工廠而把具體類暴露給客戶端的那種混亂情形吧(就好像沒了體育總局,各個俱樂部在市場上自己胡亂的尋找仔細(xì)需要的運(yùn)動員),簡單工廠就解決了這種混亂。
我們用OCP看看簡單工廠,會發(fā)現(xiàn)如果要對系統(tǒng)進(jìn)行擴(kuò)展的話治需要增加實(shí)現(xiàn)產(chǎn)品接口的產(chǎn)品類(上例表現(xiàn)為“足球運(yùn)動員”,“籃球運(yùn)動員”類,比如要增加個“乒乓球運(yùn)動員”類),而無需對原有的產(chǎn)品類進(jìn)行修改。這咋一看好像滿足OCP,但是實(shí)際上還是需要修改代碼的——對,就是修改工廠類。上例中如果增加“乒乓球運(yùn)動員”產(chǎn)品類,就必須相應(yīng)的修改“體育協(xié)會”工廠類,增加個“注冊乒乓球運(yùn)動員”方法。所以可以看出,簡單工廠模式是不滿足OCP的。
工廠方法模式及其實(shí)例 談了簡單工廠模式,下面繼續(xù)談?wù)劰S方法模式。前一節(jié)的最末點(diǎn)明了簡單工廠模式最大的缺點(diǎn)——不完全滿足OCP。為了解決這一缺點(diǎn),設(shè)計師們提出了工廠方法模式。工廠方法模式和簡單工廠模式最大的不同在于,簡單工廠模式只有一個(對于一個項(xiàng)目或者一個獨(dú)立模塊而言)工廠類,而工廠方法模式有一組實(shí)現(xiàn)了相同接口的工廠類。下面我們通過修改上一節(jié)的實(shí)例來介紹工廠方法模式。
我們在不改變產(chǎn)品類(“足球運(yùn)動員”類和“籃球運(yùn)動員”類)的情況下,修改下工廠類的結(jié)構(gòu),如下圖所示:

相關(guān)代碼如下:
| Code: |
[Copy to clipboard] |
運(yùn)動員.java public interface 運(yùn)動員 { public void 跑(); public void 跳(); }
足球運(yùn)動員.java public class 足球運(yùn)動員 implements 運(yùn)動員 {
public void 跑(){ //跑啊跑 } public void 跳(){ //跳啊跳 } }
籃球運(yùn)動員.java public class 籃球運(yùn)動員 implements 運(yùn)動員 {
public void 跑(){ //do nothing } public void 跳(){ //do nothing } }
體育協(xié)會.java public interface 體育協(xié)會 { public 運(yùn)動員 注冊(); }
足球協(xié)會.java public class 足球協(xié)會 implements 體育協(xié)會 { public 運(yùn)動員 注冊(){ return new 足球運(yùn)動員(); } }
籃球協(xié)會.java public class 籃球協(xié)會 implements 體育協(xié)會 { public 運(yùn)動員 注冊(){ return new 籃球運(yùn)動員(); } }
俱樂部.java public class 俱樂部 { private 運(yùn)動員 守門員; private 運(yùn)動員 后衛(wèi); private 運(yùn)動員 前鋒;
public void test() { 體育協(xié)會 中國足協(xié) = new 足球協(xié)會(); this.前鋒 = 中國足協(xié).注冊(); this.后衛(wèi) = 中國足協(xié).注冊();
守門員.跑(); 后衛(wèi).跳(); } } | |
很明顯可以看到,“體育協(xié)會”工廠類變成了“體育協(xié)會”接口,而實(shí)現(xiàn)此接口的分別是“足球協(xié)會”“籃球協(xié)會”等等具體的工廠類。
這樣做有什么好處呢?很明顯,這樣做就完全OCP了。如果需要再加入(或擴(kuò)展)產(chǎn)品類(比如加多個“乒乓球運(yùn)動員”)的話就不再需要修改工廠類了,而只需相應(yīng)的再添加一個實(shí)現(xiàn)了工廠接口(“體育協(xié)會”接口)的具體工廠類。
簡單工廠模式與工廠方法模式大PK 從以上對兩種模式的介紹可以了解到,工廠方法模式是為了克服簡單工廠模式的缺點(diǎn)(主要是為了滿足OCP)而設(shè)計出來的。但是,工廠方法模式就一定比簡單工廠模式好呢?筆者的答案是不一定。下面筆者將詳細(xì)比較兩種模式。
1. 結(jié)構(gòu)復(fù)雜度 從這個角度比較,顯然簡單工廠模式要占優(yōu)。簡單工廠模式只需一個工廠類,而工廠方法模式的工廠類隨著產(chǎn)品類個數(shù)增加而增加,這無疑會使類的個數(shù)越來越多,從而增加了結(jié)構(gòu)的復(fù)雜程度。
2.代碼復(fù)雜度 代碼復(fù)雜度和結(jié)構(gòu)復(fù)雜度是一對矛盾,既然簡單工廠模式在結(jié)構(gòu)方面相對簡潔,那么它在代碼方面肯定是比工廠方法模式復(fù)雜的了。簡單工廠模式的工廠類隨著產(chǎn)品類的增加需要增加很多方法(或代碼),而工廠方法模式每個具體工廠類只完成單一任務(wù),代碼簡潔。
3.客戶端編程難度 工廠方法模式雖然在工廠類結(jié)構(gòu)中引入了接口從而滿足了OCP,但是在客戶端編碼中需要對工廠類進(jìn)行實(shí)例化。而簡單工廠模式的工廠類是個靜態(tài)類,在客戶端無需實(shí)例化,這無疑是個吸引人的優(yōu)點(diǎn)。
4.管理上的難度 這是個關(guān)鍵的問題。 我們先談擴(kuò)展。眾所周知,工廠方法模式完全滿足OCP,即它有非常良好的擴(kuò)展性。那是否就說明了簡單工廠模式就沒有擴(kuò)展性呢?答案是否定的。簡單工廠模式同樣具備良好的擴(kuò)展性——擴(kuò)展的時候僅需要修改少量的代碼(修改工廠類的代碼)就可以滿足擴(kuò)展性的要求了。盡管這沒有完全滿足OCP,但筆者認(rèn)為不需要太拘泥于設(shè)計理論,要知道,sun提供的java官方工具包中也有想到多沒有滿足OCP的例子?。╦ava.util.Calendar這個抽象類就不滿足OCP,具體原因大家可以分析下)。 然后我們從維護(hù)性的角度分析下。假如某個具體產(chǎn)品類需要進(jìn)行一定的修改,很可能需要修改對應(yīng)的工廠類。當(dāng)同時需要修改多個產(chǎn)品類的時候,對工廠類的修改會變得相當(dāng)麻煩(對號入座已經(jīng)是個問題了)。反而簡單工廠沒有這些麻煩,當(dāng)多個產(chǎn)品類需要修改是,簡單工廠模式仍然僅僅需要修改唯一的工廠類(無論怎樣都能改到滿足要求吧?大不了把這個類重寫)。
由以上的分析,筆者認(rèn)為簡單工廠模式更好用更方便些。當(dāng)然這只是筆者的個人看法而已,畢竟公認(rèn)的,工廠方法模式比簡單工廠模式更“先進(jìn)”。但有時過于先進(jìn)的東西未必適合自己,這個見仁見智吧。
寫在最后 本文僅討論了兩個常見的工廠模式,筆者很主觀的分析了各自的優(yōu)缺點(diǎn),這必定會引起許多異議。如果對筆者觀點(diǎn)有意見的,很歡迎跟貼批評指出。
其實(shí)究竟哪種模式更好,可能你會回答“都不好”,呵呵,別忘了,我們還有更“先進(jìn)”的“抽象工廠模式”。。。
最后交代下版權(quán)信息。本文許多定義性的內(nèi)容引用自《java與模式》(電子工業(yè)出版社,閻宏),這是本好書,有時間有機(jī)會希望大家可以好好看看,我在此書中是獲益良多。另外筆者在寫本文過程中還參考了《UML基礎(chǔ)、案例與應(yīng)用》(人民郵電出版社,Joseph Schmuller著,李虎、趙龍剛譯),除此外其余內(nèi)容均為原創(chuàng),轉(zhuǎn)貼請注明“工大后院版權(quán)所有”。
|