類型:創(chuàng)建類模式
類圖:
抽象工廠模式與工廠方法模式的區(qū)別
抽象工廠模式是工廠方法模式的升級(jí)版本,他用來(lái)創(chuàng)建一組相關(guān)或者相互依賴的對(duì)象。他與工廠方法模式的區(qū)別就在于,工廠方法模式針對(duì)的是一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu);而抽象工廠模式則是針對(duì)的多個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)。在編程中,通常一個(gè)產(chǎn)品結(jié)構(gòu),表現(xiàn)為一個(gè)接口或者抽象類,也就是說(shuō),工廠方法模式提供的所有產(chǎn)品都是衍生自同一個(gè)接口或抽象類,而抽象工廠模式所提供的產(chǎn)品則是衍生自不同的接口或抽象類。
在抽象工廠模式中,有一個(gè)產(chǎn)品族的概念:所謂的產(chǎn)品族,是指位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中功能相關(guān)聯(lián)的產(chǎn)品組成的家族。抽象工廠模式所提供的一系列產(chǎn)品就組成一個(gè)產(chǎn)品族;而工廠方法提供的一系列產(chǎn)品稱為一個(gè)等級(jí)結(jié)構(gòu)。我們依然拿生產(chǎn)汽車的例子來(lái)說(shuō)明他們之間的區(qū)別。
在上面的類圖中,兩廂車和三廂車稱為兩個(gè)不同的等級(jí)結(jié)構(gòu);而2.0排量車和2.4排量車則稱為兩個(gè)不同的產(chǎn)品族。再具體一點(diǎn),2.0排量?jī)蓭嚭?.4排量?jī)蓭噷儆谕粋€(gè)等級(jí)結(jié)構(gòu),2.0排量三廂車和2.4排量三廂車屬于另一個(gè)等級(jí)結(jié)構(gòu);而2.0排量?jī)蓭嚭?.0排量三廂車屬于同一個(gè)產(chǎn)品族,2.4排量?jī)蓭嚭?.4排量三廂車屬于另一個(gè)產(chǎn)品族。
明白了等級(jí)結(jié)構(gòu)和產(chǎn)品族的概念,就理解工廠方法模式和抽象工廠模式的區(qū)別了,如果工廠的產(chǎn)品全部屬于同一個(gè)等級(jí)結(jié)構(gòu),則屬于工廠方法模式;如果工廠的產(chǎn)品來(lái)自多個(gè)等級(jí)結(jié)構(gòu),則屬于抽象工廠模式。在本例中,如果一個(gè)工廠模式提供2.0排量?jī)蓭嚭?.4排量?jī)蓭嚕敲此麑儆诠S方法模式;如果一個(gè)工廠模式是提供2.4排量?jī)蓭嚭?.4排量三廂車兩個(gè)產(chǎn)品,那么這個(gè)工廠模式就是抽象工廠模式,因?yàn)樗峁┑漠a(chǎn)品是分屬兩個(gè)不同的等級(jí)結(jié)構(gòu)。當(dāng)然,如果一個(gè)工廠提供全部四種車型的產(chǎn)品,因?yàn)楫a(chǎn)品分屬兩個(gè)等級(jí)結(jié)構(gòu),他當(dāng)然也屬于抽象工廠模式了。
抽象工廠模式代碼
抽象工廠模式的優(yōu)點(diǎn)
抽象工廠模式除了具有工廠方法模式的優(yōu)點(diǎn)外,最主要的優(yōu)點(diǎn)就是可以在類的內(nèi)部對(duì)產(chǎn)品族進(jìn)行約束。所謂的產(chǎn)品族,一般或多或少的都存在一定的關(guān)聯(lián),抽象工廠模式就可以在類內(nèi)部對(duì)產(chǎn)品族的關(guān)聯(lián)關(guān)系進(jìn)行定義和描述,而不必專門引入一個(gè)新的類來(lái)進(jìn)行管理。
抽象工廠模式的缺點(diǎn)
產(chǎn)品族的擴(kuò)展將是一件十分費(fèi)力的事情,假如產(chǎn)品族中需要增加一個(gè)新的產(chǎn)品,則幾乎所有的工廠類都需要進(jìn)行修改。所以使用抽象工廠模式時(shí),對(duì)產(chǎn)品等級(jí)結(jié)構(gòu)的劃分是非常重要的。
適用場(chǎng)景
當(dāng)需要?jiǎng)?chuàng)建的對(duì)象是一系列相互關(guān)聯(lián)或相互依賴的產(chǎn)品族時(shí),便可以使用抽象工廠模式。說(shuō)的更明白一點(diǎn),就是一個(gè)繼承體系中,如果存在著多個(gè)等級(jí)結(jié)構(gòu)(即存在著多個(gè)抽象類),并且分屬各個(gè)等級(jí)結(jié)構(gòu)中的實(shí)現(xiàn)類之間存在著一定的關(guān)聯(lián)或者約束,就可以使用抽象工廠模式。假如各個(gè)等級(jí)結(jié)構(gòu)中的實(shí)現(xiàn)類之間不存在關(guān)聯(lián)或約束,則使用多個(gè)獨(dú)立的工廠來(lái)對(duì)產(chǎn)品進(jìn)行創(chuàng)建,則更合適一點(diǎn)。
總結(jié)
無(wú)論是簡(jiǎn)單工廠模式,工廠方法模式,還是抽象工廠模式,他們都屬于工廠模式,在形式和特點(diǎn)上也是極為相似的,他們的最終目的都是為了解耦。在使用時(shí),我們不必去在意這個(gè)模式到底工廠方法模式還是抽象工廠模式,因?yàn)樗麄冎g的演變常常是令人琢磨不透的。經(jīng)常你會(huì)發(fā)現(xiàn),明明使用的工廠方法模式,當(dāng)新需求來(lái)臨,稍加修改,加入了一個(gè)新方法后,由于類中的產(chǎn)品構(gòu)成了不同等級(jí)結(jié)構(gòu)中的產(chǎn)品族,它就變成抽象工廠模式了;而對(duì)于抽象工廠模式,當(dāng)減少一個(gè)方法使的提供的產(chǎn)品不再構(gòu)成產(chǎn)品族之后,它就演變成了工廠方法模式。
所以,在使用工廠模式時(shí),只需要關(guān)心降低耦合度的目的是否達(dá)到了。
定義:定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類,工廠方法使一個(gè)類的實(shí)例化延遲到其子類。
類型:創(chuàng)建類模式
類圖:
工廠方法模式代碼
工廠模式:
首先需要說(shuō)一下工廠模式。工廠模式根據(jù)抽象程度的不同分為三種:簡(jiǎn)單工廠模式(也叫靜態(tài)工廠模式)、本文所講述的工廠方法模式、以及抽象工廠模式。工廠模式是編程中經(jīng)常用到的一種模式。它的主要優(yōu)點(diǎn)有:
工廠方法模式:
通過(guò)工廠方法模式的類圖可以看到,工廠方法模式有四個(gè)要素:
前文提到的簡(jiǎn)單工廠模式跟工廠方法模式極為相似,區(qū)別是:簡(jiǎn)單工廠只有三個(gè)要素,他沒(méi)有工廠接口,并且得到產(chǎn)品的方法一般是靜態(tài)的。因?yàn)闆](méi)有工廠接口,所以在工廠實(shí)現(xiàn)的擴(kuò)展性方面稍弱,可以算所工廠方法模式的簡(jiǎn)化版,關(guān)于簡(jiǎn)單工廠模式,在此一筆帶過(guò)。
適用場(chǎng)景:
不管是簡(jiǎn)單工廠模式,工廠方法模式還是抽象工廠模式,他們具有類似的特性,所以他們的適用場(chǎng)景也是類似的。
首先,作為一種創(chuàng)建類模式,在任何需要生成復(fù)雜對(duì)象的地方,都可以使用工廠方法模式。有一點(diǎn)需要注意的地方就是復(fù)雜對(duì)象適合使用工廠模式,而簡(jiǎn)單對(duì)象,特別是只需要通過(guò)new就可以完成創(chuàng)建的對(duì)象,無(wú)需使用工廠模式。如果使用工廠模式,就需要引入一個(gè)工廠類,會(huì)增加系統(tǒng)的復(fù)雜度。
其次,工廠模式是一種典型的解耦模式,迪米特法則在工廠模式中表現(xiàn)的尤為明顯。假如調(diào)用者自己組裝產(chǎn)品需要增加依賴關(guān)系時(shí),可以考慮使用工廠模式。將會(huì)大大降低對(duì)象之間的耦合度。
再次,由于工廠模式是依靠抽象架構(gòu)的,它把實(shí)例化產(chǎn)品的任務(wù)交由實(shí)現(xiàn)類完成,擴(kuò)展性比較好。也就是說(shuō),當(dāng)需要系統(tǒng)有比較好的擴(kuò)展性時(shí),可以考慮工廠模式,不同的產(chǎn)品用不同的實(shí)現(xiàn)工廠來(lái)組裝。
典型應(yīng)用
要說(shuō)明工廠模式的優(yōu)點(diǎn),可能沒(méi)有比組裝汽車更合適的例子了。場(chǎng)景是這樣的:汽車由發(fā)動(dòng)機(jī)、輪、底盤組成,現(xiàn)在需要組裝一輛車交給調(diào)用者。假如不使用工廠模式,代碼如下:
可以看到,調(diào)用者為了組裝汽車還需要另外實(shí)例化發(fā)動(dòng)機(jī)、底盤和輪胎,而這些汽車的組件是與調(diào)用者無(wú)關(guān)的,嚴(yán)重違反了迪米特法則,耦合度太高。并且非常不利于擴(kuò)展。另外,本例中發(fā)動(dòng)機(jī)、底盤和輪胎還是比較具體的,在實(shí)際應(yīng)用中,可能這些產(chǎn)品的組件也都是抽象的,調(diào)用者根本不知道怎樣組裝產(chǎn)品。假如使用工廠方法的話,整個(gè)架構(gòu)就顯得清晰了許多。
使用工廠方法后,調(diào)用端的耦合度大大降低了。并且對(duì)于工廠來(lái)說(shuō),是可以擴(kuò)展的,以后如果想組裝其他的汽車,只需要再增加一個(gè)工廠類的實(shí)現(xiàn)就可以。無(wú)論是靈活性還是穩(wěn)定性都得到了極大的提高。
定義:確保一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。類型:創(chuàng)建類模式
類圖:
類圖知識(shí)點(diǎn):
1.類圖分為三部分,依次是類名、屬性、方法
2.以<<開(kāi)頭和以>>結(jié)尾的為注釋信息
3.修飾符+代表public,-代表private,#代表protected,什么都沒(méi)有代表包可見(jiàn)。
4.帶下劃線的屬性或方法代表是靜態(tài)的。
5.對(duì)類圖中對(duì)象的關(guān)系不熟悉的朋友可以參考文章:設(shè)計(jì)模式中類的關(guān)系。
單例模式應(yīng)該是23種設(shè)計(jì)模式中最簡(jiǎn)單的一種模式了。它有以下幾個(gè)要素:
單例模式根據(jù)實(shí)例化對(duì)象時(shí)機(jī)的不同分為兩種:一種是餓漢式單例,一種是懶漢式單例。餓漢式單例在單例類被加載時(shí)候,就實(shí)例化一個(gè)對(duì)象交給自己的引用;而懶漢式在調(diào)用取得實(shí)例方法的時(shí)候才會(huì)實(shí)例化對(duì)象。代碼如下:
餓漢式單例
懶漢式單例
單例模式的優(yōu)點(diǎn):
適用場(chǎng)景:由于單例模式的以上優(yōu)點(diǎn),所以是編程中用的比較多的一種設(shè)計(jì)模式。我總結(jié)了一下我所知道的適合使用單例模式的場(chǎng)景:
單例模式注意事項(xiàng):
關(guān)于java中單例模式的一些爭(zhēng)議:
單例模式的對(duì)象長(zhǎng)時(shí)間不用會(huì)被jvm垃圾收集器收集嗎
看到不少資料中說(shuō):如果一個(gè)單例對(duì)象在內(nèi)存中長(zhǎng)久不用,會(huì)被jvm認(rèn)為是一個(gè)垃圾,在執(zhí)行垃圾收集的時(shí)候會(huì)被清理掉。對(duì)此這個(gè)說(shuō)法,筆者持懷疑態(tài)度,筆者本人的觀點(diǎn)是:在hotspot虛擬機(jī)1.6版本中,除非人為地?cái)嚅_(kāi)單例中靜態(tài)引用到單例對(duì)象的聯(lián)接,否則jvm垃圾收集器是不會(huì)回收單例對(duì)象的。
對(duì)于這個(gè)爭(zhēng)議,筆者單獨(dú)寫了一篇文章進(jìn)行討論,如果您有不同的觀點(diǎn)或者有過(guò)這方面的經(jīng)歷請(qǐng)進(jìn)入文章單例模式討論篇:?jiǎn)卫J脚c垃圾收集參與討論。
在一個(gè)jvm中會(huì)出現(xiàn)多個(gè)單例嗎
在分布式系統(tǒng)、多個(gè)類加載器、以及序列化的的情況下,會(huì)產(chǎn)生多個(gè)單例,這一點(diǎn)是無(wú)庸置疑的。那么在同一個(gè)jvm中,會(huì)不會(huì)產(chǎn)生單例呢?使用單例提供的getInstance()方法只能得到同一個(gè)單例,除非是使用反射方式,將會(huì)得到新的單例。代碼如下
這樣,每次運(yùn)行都會(huì)產(chǎn)生新的單例對(duì)象。所以運(yùn)用單例模式時(shí),一定注意不要使用反射產(chǎn)生新的單例對(duì)象。
懶漢式單例線程安全嗎
主要是網(wǎng)上的一些說(shuō)法,懶漢式的單例模式是線程不安全的,即使是在實(shí)例化對(duì)象的方法上加synchronized關(guān)鍵字,也依然是危險(xiǎn)的,但是筆者經(jīng)過(guò)編碼測(cè)試,發(fā)現(xiàn)加synchronized關(guān)鍵字修飾后,雖然對(duì)性能有部分影響,但是卻是線程安全的,并不會(huì)產(chǎn)生實(shí)例化多個(gè)對(duì)象的情況。
單例模式只有餓漢式和懶漢式兩種嗎
餓漢式單例和懶漢式單例只是兩種比較主流和常用的單例模式方法,從理論上講,任何可以實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例的設(shè)計(jì)模式,都可以稱為單例模式。
單例類可以被繼承嗎
餓漢式單例和懶漢式單例由于構(gòu)造方法是private的,所以他們都是不可繼承的,但是其他很多單例模式是可以繼承的,例如登記式單例。
餓漢式單例好還是懶漢式單例好
在java中,餓漢式單例要優(yōu)于懶漢式單例。C++中則一般使用懶漢式單例。
單例模式比較簡(jiǎn)單,在此就不舉例代碼演示了。
單例模式與垃圾回收
Jvm的垃圾回收機(jī)制到底會(huì)不會(huì)回收掉長(zhǎng)時(shí)間不用的單例模式對(duì)象,這的確是一個(gè)比較有爭(zhēng)議性的問(wèn)題。將這一部分內(nèi)容單獨(dú)成篇的目的也是為了與廣大博友廣泛的討論一下這個(gè)問(wèn)題。為了能讓更多的人看到這篇文章,請(qǐng)各位博友看完文章之后,點(diǎn)一下“頂”,讓本篇文章排名盡量的靠前。筆者在此謝過(guò)。
討論命題:當(dāng)一個(gè)單例的對(duì)象長(zhǎng)久不用時(shí),會(huì)不會(huì)被jvm的垃圾收集機(jī)制回收。
首先說(shuō)一下為什么會(huì)產(chǎn)生這一疑問(wèn),筆者本人再此之前從來(lái)沒(méi)有考慮過(guò)垃圾回收對(duì)單例模式的影響,直到去年讀了一本書,《設(shè)計(jì)模式之禪》秦小波著。在書中提到在j2ee應(yīng)用中,jvm垃圾回收機(jī)制會(huì)把長(zhǎng)久不用的單例類對(duì)象當(dāng)作垃圾,并在cpu空閑的時(shí)候?qū)ζ溥M(jìn)行回收。之前讀過(guò)的幾本設(shè)計(jì)模式的書,包括《java與模式》,書中都沒(méi)有提到j(luò)vm垃圾回收機(jī)制對(duì)單例的影響。并且在工作過(guò)程中,也沒(méi)有過(guò)單例對(duì)象被回收的經(jīng)歷,加上工作中很多前輩曾經(jīng)告誡過(guò)筆者:盡量不要聲明太多的靜態(tài)屬性,因?yàn)檫@些靜態(tài)屬性被加載后不會(huì)被釋放。因此對(duì)jvm垃圾收集會(huì)回收單例對(duì)象這一說(shuō)法持懷疑態(tài)度。漸漸地,發(fā)現(xiàn)在同事中和網(wǎng)上的技術(shù)人員中,對(duì)這一問(wèn)題也基本上是鮮明的對(duì)立兩派。那么到底jvm會(huì)不會(huì)回收長(zhǎng)久不用的單例對(duì)象呢。
對(duì)這一問(wèn)題,筆者本人的觀點(diǎn)是:不會(huì)回收。
下面給出本人的測(cè)試代碼
本段程序的目的是模擬j2ee容器,首先實(shí)例化單例類,這個(gè)單例類占6M內(nèi)存,然后程序進(jìn)入死循環(huán),不斷的創(chuàng)建對(duì)象,逼迫jvm進(jìn)行垃圾回收,然后觀察垃圾收集信息,如果進(jìn)行垃圾收集后,內(nèi)存仍然大于6M,則說(shuō)明垃圾回收不會(huì)回收單例對(duì)象。
運(yùn)行本程序使用的虛擬機(jī)是hotspot虛擬機(jī),也就是我們使用的最多的java官方提供的虛擬機(jī),俗稱jdk,版本是jdk1.6.0_12
運(yùn)行時(shí)vm arguments參數(shù)為:-verbose:gc -Xms20M -Xmx20M,意思是每次jvm進(jìn)行垃圾回收時(shí)顯示內(nèi)存信息,jvm的內(nèi)存設(shè)為固定20M。
運(yùn)行結(jié)果:
……
[Full GC 18566K->6278K(20352K), 0.0101066 secs]
[GC 18567K->18566K(20352K), 0.0001978 secs]
[Full GC 18566K->6278K(20352K), 0.0088229 secs]
……
從運(yùn)行結(jié)果中可以看到總有6M空間沒(méi)有被收集。因此,筆者認(rèn)為,至少在hotspot虛擬機(jī)中,垃圾回收是不會(huì)回收單例對(duì)象的。
后來(lái)查閱了一些相關(guān)的資料,hotspot虛擬機(jī)的垃圾收集算法使用根搜索算法。這個(gè)算法的基本思路是:對(duì)任何“活”的對(duì)象,一定能最終追溯到其存活在堆棧或靜態(tài)存儲(chǔ)區(qū)之中的引用。通過(guò)一系列名為根(GC Roots)的引用作為起點(diǎn),從這些根開(kāi)始搜索,經(jīng)過(guò)一系列的路徑,如果可以到達(dá)java堆中的對(duì)象,那么這個(gè)對(duì)象就是“活”的,是不可回收的。可以作為根的對(duì)象有:
方法區(qū)是jvm的一塊內(nèi)存區(qū)域,用來(lái)存放類相關(guān)的信息。很明顯,java中單例模式創(chuàng)建的對(duì)象被自己類中的靜態(tài)屬性所引用,符合第二條,因此,單例對(duì)象不會(huì)被jvm垃圾收集。
雖然jvm堆中的單例對(duì)象不會(huì)被垃圾收集,但是單例類本身如果長(zhǎng)時(shí)間不用會(huì)不會(huì)被收集呢?因?yàn)閖vm對(duì)方法區(qū)也是有垃圾收集機(jī)制的。如果單例類被收集,那么堆中的對(duì)象就會(huì)失去到根的路徑,必然會(huì)被垃圾收集掉。對(duì)此,筆者查閱了hotspot虛擬機(jī)對(duì)方法區(qū)的垃圾收集方法,jvm卸載類的判定條件如下:
只有三個(gè)條件都滿足,jvm才會(huì)在垃圾收集的時(shí)候卸載類。顯然,單例的類不滿足條件一,因此單例類也不會(huì)被卸載。也就是說(shuō),只要單例類中的靜態(tài)引用指向jvm堆中的單例對(duì)象,那么單例類和單例對(duì)象都不會(huì)被垃圾收集,依據(jù)根搜索算法,對(duì)象是否會(huì)被垃圾收集與未被使用時(shí)間長(zhǎng)短無(wú)關(guān),僅僅在于這個(gè)對(duì)象是不是“活”的。假如一個(gè)對(duì)象長(zhǎng)久未使用而被回收,那么收集算法應(yīng)該是最近最長(zhǎng)未使用算法,最近最長(zhǎng)未使用算法一般用在操作系統(tǒng)的內(nèi)外存交換中,如果用在虛擬機(jī)垃圾回收中,豈不是太不安全了?以上是筆者的觀點(diǎn)。
因此筆者的觀點(diǎn)是:在hotspot虛擬機(jī)1.6版本中,除非人為地?cái)嚅_(kāi)單例中靜態(tài)引用到單例對(duì)象的聯(lián)接,否則jvm垃圾收集器是不會(huì)回收單例對(duì)象的。
簡(jiǎn)單工廠模式有一個(gè)問(wèn)題就是,類的創(chuàng)建依賴工廠類,也就是說(shuō),如果想要拓展程序,必須對(duì)工廠類進(jìn)行修改,這違背了閉包原則,所以,從設(shè)計(jì)角度考慮,有一定的問(wèn)題,如何解決?就用到工廠方法模式,創(chuàng)建一個(gè)工廠接口和創(chuàng)建多個(gè)工廠實(shí)現(xiàn)類,這樣一旦需要增加新的功能,直接增加新的工廠類就可以了,不需要修改之前的代碼。
請(qǐng)看例子:
兩個(gè)實(shí)現(xiàn)類:
兩個(gè)工廠類:
在提供一個(gè)接口:
測(cè)試類:
其實(shí)這個(gè)模式的好處就是,如果你現(xiàn)在想增加一個(gè)功能:發(fā)及時(shí)信息,則只需做一個(gè)實(shí)現(xiàn)類,實(shí)現(xiàn)Sender接口,同時(shí)做一個(gè)工廠類,實(shí)現(xiàn)Provider接口,就OK了,無(wú)需去改動(dòng)現(xiàn)成的代碼。這樣做,拓展性較好!
工廠方法模式和抽象工廠模式不好分清楚,他們的區(qū)別如下:
工廠方法模式: 一個(gè)抽象產(chǎn)品類,可以派生出多個(gè)具體產(chǎn)品類。 一個(gè)抽象工廠類,可以派生出多個(gè)具體工廠類。 每個(gè)具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例。 抽象工廠模式: 多個(gè)抽象產(chǎn)品類,每個(gè)抽象產(chǎn)品類可以派生出多個(gè)具體產(chǎn)品類。 一個(gè)抽象工廠類,可以派生出多個(gè)具體工廠類。 每個(gè)具體工廠類可以創(chuàng)建多個(gè)具體產(chǎn)品類的實(shí)例,也就是創(chuàng)建的是一個(gè)產(chǎn)品線下的多個(gè)產(chǎn)品。 區(qū)別: 工廠方法模式只有一個(gè)抽象產(chǎn)品類,而抽象工廠模式有多個(gè)。 工廠方法模式的具體工廠類只能創(chuàng)建一個(gè)具體產(chǎn)品類的實(shí)例,而抽象工廠模式可以創(chuàng)建多個(gè)。
工廠方法創(chuàng)建 "一種" 產(chǎn)品,他的著重點(diǎn)在于"怎么創(chuàng)建",也就是說(shuō)如果你開(kāi)發(fā),你的大量代碼很可能圍繞著這種產(chǎn)品的構(gòu)造,初始化這些細(xì)節(jié)上面。也因?yàn)槿绱耍愃频漠a(chǎn)品之間有很多可以復(fù)用的特征,所以會(huì)和模版方法相隨。
抽象工廠需要?jiǎng)?chuàng)建一些列產(chǎn)品,著重點(diǎn)在于"創(chuàng)建哪些"產(chǎn)品上,也就是說(shuō),如果你開(kāi)發(fā),你的主要任務(wù)是劃分不同差異的產(chǎn)品線,并且盡量保持每條產(chǎn)品線接口一致,從而可以從同一個(gè)抽象工廠繼承。
對(duì)于java來(lái)說(shuō),你能見(jiàn)到的大部分抽象工廠模式都是這樣的: ---它的里面是一堆工廠方法,每個(gè)工廠方法返回某種類型的對(duì)象。 比如說(shuō)工廠可以生產(chǎn)鼠標(biāo)和鍵盤。那么抽象工廠的實(shí)現(xiàn)類(它的某個(gè)具體子類)的對(duì)象都可以生產(chǎn)鼠標(biāo)和鍵盤,但可能工廠A生產(chǎn)的是羅技的鍵盤和鼠標(biāo),工廠B是微軟的。 這樣A和B就是工廠,對(duì)應(yīng)于抽象工廠; 每個(gè)工廠生產(chǎn)的鼠標(biāo)和鍵盤就是產(chǎn)品,對(duì)應(yīng)于工廠方法; 用了工廠方法模式,你替換生成鍵盤的工廠方法,就可以把鍵盤從羅技換到微軟。但是用了抽象工廠模式,你只要換家工廠,就可以同時(shí)替換鼠標(biāo)和鍵盤一套。如果你要的產(chǎn)品有幾十個(gè),當(dāng)然用抽象工廠模式一次替換全部最方便(這個(gè)工廠會(huì)替你用相應(yīng)的工廠方法) 所以說(shuō)抽象工廠就像工廠,而工廠方法則像是工廠的一種產(chǎn)品生產(chǎn)線
簡(jiǎn)單工廠模式模式分為三種:
就是建立一個(gè)工廠類,對(duì)實(shí)現(xiàn)了同一接口的一些類進(jìn)行實(shí)例的創(chuàng)建。首先看下關(guān)系圖:
舉例如下:(我們舉一個(gè)發(fā)送郵件和短信的例子)
首先,創(chuàng)建二者的共同接口:
其次,創(chuàng)建實(shí)現(xiàn)類:
最后,建工廠類:
我們來(lái)測(cè)試下:
輸出:this is sms sender!
是對(duì)普通工廠方法模式的改進(jìn),在普通工廠方法模式中,如果傳遞的字符串出錯(cuò),則不能正確創(chuàng)建對(duì)象,而多個(gè)工廠方法模式是提供多個(gè)工廠方法,分別創(chuàng)建對(duì)象。關(guān)系圖:
將上面的代碼做下修改,改動(dòng)下SendFactory類就行,如下:
測(cè)試類如下:
輸出:this is mailsender!
將上面的多個(gè)工廠方法模式里的方法置為靜態(tài)的,不需要?jiǎng)?chuàng)建實(shí)例,直接調(diào)用即可。
輸出:this is mailsender!
總體來(lái)說(shuō),工廠模式適合:凡是出現(xiàn)了大量的產(chǎn)品需要?jiǎng)?chuàng)建,并且具有共同的接口時(shí),可以通過(guò)工廠方法模式進(jìn)行創(chuàng)建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正確創(chuàng)建對(duì)象,第三種相對(duì)于第二種,不需要實(shí)例化工廠類,所以,大多數(shù)情況下,我們會(huì)選用第三種——靜態(tài)工廠方法模式。
簡(jiǎn)單工廠模式解釋:
簡(jiǎn)單工廠模式(Simple Factory Pattern)屬于類的創(chuàng)新型模式,又叫靜態(tài)工廠方法模式(Static FactoryMethod Pattern),是通過(guò)專門定義一個(gè)類來(lái)負(fù)責(zé)創(chuàng)建其他類的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類。
簡(jiǎn)單工廠模式的UML圖:
簡(jiǎn)單工廠模式中包含的角色及其相應(yīng)的職責(zé)如下:
工廠角色(Creator):這是簡(jiǎn)單工廠模式的核心,由它負(fù)責(zé)創(chuàng)建所有的類的內(nèi)部邏輯。當(dāng)然工廠類必須能夠被外界調(diào)用,創(chuàng)建所需要的產(chǎn)品對(duì)象。
抽象(Product)產(chǎn)品角色:簡(jiǎn)單工廠模式所創(chuàng)建的所有對(duì)象的父類,注意,這里的父類可以是接口也可以是抽象類,它負(fù)責(zé)描述所有實(shí)例所共有的公共接口。
具體產(chǎn)品(Concrete Product)角色:簡(jiǎn)單工廠所創(chuàng)建的具體實(shí)例對(duì)象,這些具體的產(chǎn)品往往都擁有共同的父類。
簡(jiǎn)單工廠模式深入分析:
簡(jiǎn)單工廠模式解決的問(wèn)題是如何去實(shí)例化一個(gè)合適的對(duì)象。
簡(jiǎn)單工廠模式的核心思想就是:有一個(gè)專門的類來(lái)負(fù)責(zé)創(chuàng)建實(shí)例的過(guò)程。
具體來(lái)說(shuō),把產(chǎn)品看著是一系列的類的集合,這些類是由某個(gè)抽象類或者接口派生出來(lái)的一個(gè)對(duì)象樹(shù)。而工廠類用來(lái)產(chǎn)生一個(gè)合適的對(duì)象來(lái)滿足客戶的要求。
如果簡(jiǎn)單工廠模式所涉及到的具體產(chǎn)品之間沒(méi)有共同的邏輯,那么我們就可以使用接口來(lái)扮演抽象產(chǎn)品的角色;如果具體產(chǎn)品之間有功能的邏輯或,我們就必須把這些共同的東西提取出來(lái),放在一個(gè)抽象類中,然后讓具體產(chǎn)品繼承抽象類。為實(shí)現(xiàn)更好復(fù)用的目的,共同的東西總是應(yīng)該抽象出來(lái)的。
在實(shí)際的的使用中,抽閑產(chǎn)品和具體產(chǎn)品之間往往是多層次的產(chǎn)品結(jié)構(gòu),如下圖所示:
簡(jiǎn)單工廠模式使用場(chǎng)景分析及代碼實(shí)現(xiàn):
GG請(qǐng)自己的女朋友和眾多美女吃飯,但是GG自己是不會(huì)做飯的或者做的飯很不好,這說(shuō)明GG不用自己去創(chuàng)建各種食物的對(duì)象;各個(gè)美女都有各自的愛(ài)好,到麥當(dāng)勞后她們喜歡吃什么直接去點(diǎn)就行了,麥當(dāng)勞就是生產(chǎn)各種食物的工廠,這時(shí)候GG不用自己動(dòng)手,也可以請(qǐng)這么多美女吃飯,所要做的就是買單O(∩_∩)O哈哈~,其UML圖如下所示:
實(shí)現(xiàn)代碼如下:
新建立一個(gè)食物的接口:
package com.diermeng.designPattern.SimpleFactory;
/* * 產(chǎn)品的抽象接口 */ public interface Food { /* * 獲得相應(yīng)的食物 */ public void get(); } |
接下來(lái)建立具體的產(chǎn)品:麥香雞和薯?xiàng)l
package com.diermeng.designPattern.SimpleFactory.impl; import com.diermeng.designPattern.SimpleFactory.Food;
/* * 麥香雞對(duì)抽象產(chǎn)品接口的實(shí)現(xiàn) */ public class McChicken implements Food{ /* * 獲取一份麥香雞 */ public void get(){ System.out.println("我要一份麥香雞"); } } |
package com.diermeng.designPattern.SimpleFactory.impl; import com.diermeng.designPattern.SimpleFactory.Food;
/* * 薯?xiàng)l對(duì)抽象產(chǎn)品接口的實(shí)現(xiàn) */ public class Chips implements Food{ /* * 獲取一份薯?xiàng)l */ public void get(){ System.out.println("我要一份薯?xiàng)l"); } } |
現(xiàn)在建立一個(gè)食物加工工廠:
package com.diermeng.designPattern.SimpleFactory.impl; import com.diermeng.designPattern.SimpleFactory.Food;
public class FoodFactory {
public static Food getFood(String type) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if(type.equalsIgnoreCase("mcchicken")) { return McChicken.class.newInstance();
} else if(type.equalsIgnoreCase("chips")) { return Chips.class.newInstance(); } else { System.out.println("哎呀!找不到相應(yīng)的實(shí)例化類啦!"); return null; }
} } |
最后我們建立測(cè)試客戶端:
package com.diermeng.designPattern.SimpleFactory.client; import com.diermeng.designPattern.SimpleFactory.Food; import com.diermeng.designPattern.SimpleFactory.impl.FoodFactory;
/* * 測(cè)試客戶端 */ public class SimpleFactoryTest { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//實(shí)例化各種食物 Food mcChicken = FoodFactory.getFood("McChicken"); Food chips = FoodFactory.getFood("Chips"); Food eggs = FoodFactory.getFood("Eggs");
//獲取食物 if(mcChicken!=null){ mcChicken.get(); } if(chips!=null){ chips.get(); } if(eggs!=null){ eggs.get(); }
} } |
輸出的結(jié)果如下:
哎呀!找不到相應(yīng)的實(shí)例化類啦! 我要一份麥香雞 我要一份薯?xiàng)l |
簡(jiǎn)單工廠模式的優(yōu)缺點(diǎn)分析:
優(yōu)點(diǎn):工廠類是整個(gè)模式的關(guān)鍵所在。它包含必要的判斷邏輯,能夠根據(jù)外界給定的信息,決定究竟應(yīng)該創(chuàng)建哪個(gè)具體類的對(duì)象。用戶在使用時(shí)可以直接根據(jù)工廠類去創(chuàng)建所需的實(shí)例,而無(wú)需了解這些對(duì)象是如何創(chuàng)建以及如何組織的。有利于整個(gè)軟件體系結(jié)構(gòu)的優(yōu)化。
缺點(diǎn):由于工廠類集中了所有實(shí)例的創(chuàng)建邏輯,這就直接導(dǎo)致一旦這個(gè)工廠出了問(wèn)題,所有的客戶端都會(huì)受到牽連;而且由于簡(jiǎn)單工廠模式的產(chǎn)品室基于一個(gè)共同的抽象類或者接口,這樣一來(lái),但產(chǎn)品的種類增加的時(shí)候,即有不同的產(chǎn)品接口或者抽象類的時(shí)候,工廠類就需要判斷何時(shí)創(chuàng)建何種種類的產(chǎn)品,這就和創(chuàng)建何種種類產(chǎn)品的產(chǎn)品相互混淆在了一起,違背了單一職責(zé),導(dǎo)致系統(tǒng)喪失靈活性和可維護(hù)性。而且更重要的是,簡(jiǎn)單工廠模式違背了“開(kāi)放封閉原則”,就是違背了“系統(tǒng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉”的原則,因?yàn)楫?dāng)我新增加一個(gè)產(chǎn)品的時(shí)候必須修改工廠類,相應(yīng)的工廠類就需要重新編譯一遍。
總結(jié)一下:簡(jiǎn)單工廠模式分離產(chǎn)品的創(chuàng)建者和消費(fèi)者,有利于軟件系統(tǒng)結(jié)構(gòu)的優(yōu)化;但是由于一切邏輯都集中在一個(gè)工廠類中,導(dǎo)致了沒(méi)有很高的內(nèi)聚性,同時(shí)也違背了“開(kāi)放封閉原則”。另外,簡(jiǎn)單工廠模式的方法一般都是靜態(tài)的,而靜態(tài)工廠方法是無(wú)法讓子類繼承的,因此,簡(jiǎn)單工廠模式無(wú)法形成基于基類的繼承樹(shù)結(jié)構(gòu)。
簡(jiǎn)單工廠模式的實(shí)際應(yīng)用簡(jiǎn)介:
作為一個(gè)最基本和最簡(jiǎn)單的設(shè)計(jì)模式,簡(jiǎn)單工廠模式卻有很非常廣泛的應(yīng)用,我們這里以Java中的JDBC操作數(shù)據(jù)庫(kù)為例來(lái)說(shuō)明。
JDBC是SUN公司提供的一套數(shù)據(jù)庫(kù)編程接口API,它利用Java語(yǔ)言提供簡(jiǎn)單、一致的方式來(lái)訪問(wèn)各種關(guān)系型數(shù)據(jù)庫(kù)。Java程序通過(guò)JDBC可以執(zhí)行SQL語(yǔ)句,對(duì)獲取的數(shù)據(jù)進(jìn)行處理,并將變化了的數(shù)據(jù)存回?cái)?shù)據(jù)庫(kù),因此,JDBC是Java應(yīng)用程序與各種關(guān)系數(shù)據(jù)進(jìn)行對(duì)話的一種機(jī)制。用JDBC進(jìn)行數(shù)據(jù)庫(kù)訪問(wèn)時(shí),要使用數(shù)據(jù)庫(kù)廠商提供的驅(qū)動(dòng)程序接口與數(shù)據(jù)庫(kù)管理系統(tǒng)進(jìn)行數(shù)據(jù)交互。
客戶端要使用使用數(shù)據(jù)時(shí),只需要和工廠進(jìn)行交互即可,這就導(dǎo)致操作步驟得到極大的簡(jiǎn)化,操作步驟按照順序依次為:注冊(cè)并加載數(shù)據(jù)庫(kù)驅(qū)動(dòng),一般使用Class.forName();創(chuàng)建與數(shù)據(jù)庫(kù)的鏈接Connection對(duì)象;創(chuàng)建SQL語(yǔ)句對(duì)象preparedStatement(sql);提交SQL語(yǔ)句,根據(jù)實(shí)際情況使用executeQuery()或者executeUpdate();顯示相應(yīng)的結(jié)果;關(guān)閉數(shù)據(jù)庫(kù)。
在UML類圖中,常見(jiàn)的有以下幾種關(guān)系: 泛化(Generalization), 實(shí)現(xiàn)(Realization),關(guān)聯(lián)(Association),聚合(Aggregation),組合(Composition),依賴(Dependency)
1. 泛化(Generalization)
【泛化關(guān)系】:是一種繼承關(guān)系,表示一般與特殊的關(guān)系,它指定了子類如何特化父類的所有特征和行為。例如:老虎是動(dòng)物的一種,即有老虎的特性也有動(dòng)物的共性。
【箭頭指向】:帶三角箭頭的實(shí)線,箭頭指向父類
2. 實(shí)現(xiàn)(Realization)
【實(shí)現(xiàn)關(guān)系】:是一種類與接口的關(guān)系,表示類是接口所有特征和行為的實(shí)現(xiàn).
【箭頭指向】:帶三角箭頭的虛線,箭頭指向接口
3. 關(guān)聯(lián)(Association)
【關(guān)聯(lián)關(guān)系】:是一種擁有的關(guān)系,它使一個(gè)類知道另一個(gè)類的屬性和方法;如:老師與學(xué)生,丈夫與妻子關(guān)聯(lián)可以是雙向的,也可以是單向的。雙向的關(guān)聯(lián)可以有兩個(gè)箭頭或者沒(méi)有箭頭,單向的關(guān)聯(lián)有一個(gè)箭頭。
【代碼體現(xiàn)】:成員變量
【箭頭及指向】:帶普通箭頭的實(shí)心線,指向被擁有者
上圖中,老師與學(xué)生是雙向關(guān)聯(lián),老師有多名學(xué)生,學(xué)生也可能有多名老師。但學(xué)生與某課程間的關(guān)系為單向關(guān)聯(lián),一名學(xué)生可能要上多門課程,課程是個(gè)抽象的東西他不擁有學(xué)生。
下圖為自身關(guān)聯(lián):
4. 聚合(Aggregation)
【聚合關(guān)系】:是整體與部分的關(guān)系,且部分可以離開(kāi)整體而單獨(dú)存在。如車和輪胎是整體和部分的關(guān)系,輪胎離開(kāi)車仍然可以存在。
聚合關(guān)系是關(guān)聯(lián)關(guān)系的一種,是強(qiáng)的關(guān)聯(lián)關(guān)系;關(guān)聯(lián)和聚合在語(yǔ)法上無(wú)法區(qū)分,必須考察具體的邏輯關(guān)系。
【代碼體現(xiàn)】:成員變量
【箭頭及指向】:帶空心菱形的實(shí)心線,菱形指向整體
5. 組合(Composition)
【組合關(guān)系】:是整體與部分的關(guān)系,但部分不能離開(kāi)整體而單獨(dú)存在。如公司和部門是整體和部分的關(guān)系,沒(méi)有公司就不存在部門。
組合關(guān)系是關(guān)聯(lián)關(guān)系的一種,是比聚合關(guān)系還要強(qiáng)的關(guān)系,它要求普通的聚合關(guān)系中代表整體的對(duì)象負(fù)責(zé)代表部分的對(duì)象的生命周期。
【代碼體現(xiàn)】:成員變量
【箭頭及指向】:帶實(shí)心菱形的實(shí)線,菱形指向整體
6. 依賴(Dependency)
【依賴關(guān)系】:是一種使用的關(guān)系,即一個(gè)類的實(shí)現(xiàn)需要另一個(gè)類的協(xié)助,所以要盡量不使用雙向的互相依賴.
【代碼表現(xiàn)】:局部變量、方法的參數(shù)或者對(duì)靜態(tài)方法的調(diào)用
【箭頭及指向】:帶箭頭的虛線,指向被使用者
各種關(guān)系的強(qiáng)弱順序:
泛化 = 實(shí)現(xiàn) > 組合 > 聚合 > 關(guān)聯(lián) > 依賴
下面這張UML圖,比較形象地展示了各種類圖關(guān)系: