摘要:又稱為多態(tài)性工廠模式或虛擬構(gòu)造子模式。簡(jiǎn)單工廠模式簡(jiǎn)單工廠模式簡(jiǎn)單工廠模式又稱為靜態(tài)工廠方法模式,它屬于類創(chuàng)建型模式。多態(tài)性設(shè)計(jì)工廠方法模式之所以又被稱為多態(tài)工廠模式,是因?yàn)樗械木唧w工廠類都具有同一抽象父類。
點(diǎn)擊進(jìn)入我的博客 2.1 簡(jiǎn)單工廠模式 2.1.1 工廠模式的幾種形態(tài)
工廠模式主要用一下幾種形態(tài):
簡(jiǎn)單工廠(Simple Factory):專門定義一個(gè)類來負(fù)責(zé)創(chuàng)建其他類的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類。它又稱為靜態(tài)工廠方法模式。
工廠方法(Factory Method):提前定義用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化具體的某一個(gè)類,即在工廠和產(chǎn)品中間增加接口,工廠不再負(fù)責(zé)產(chǎn)品的創(chuàng)建,由接口針對(duì)不同條件返回具體的類實(shí)例,由具體類實(shí)例去實(shí)現(xiàn)。又稱為多態(tài)性工廠模式或虛擬構(gòu)造子模式。
抽象工廠(Abstract Factory):抽象工廠模式是指當(dāng)有多個(gè)抽象角色時(shí),使用的一種工廠模式。抽象工廠模式可以向客戶端提供一個(gè)接口,使客戶端在不必指定產(chǎn)品的具體的情況下,創(chuàng)建多個(gè)產(chǎn)品族中的產(chǎn)品對(duì)象。又稱為工具箱模式。
2.1.2 簡(jiǎn)單工廠模式簡(jiǎn)單工廠模式(Simple Factory Pattern)又稱為靜態(tài)工廠方法(Static Factory Method)模式,它屬于類創(chuàng)建型模式。
在簡(jiǎn)單工廠模式中,可以根據(jù)自變量的不同返回不同類的實(shí)例。
簡(jiǎn)單工廠模式專門定義一個(gè)類來負(fù)責(zé)創(chuàng)建其他類的實(shí)例,被創(chuàng)建的實(shí)例通常都具有共同的父類。
工廠類(Creator)角色:擔(dān)任這個(gè)角色的是工廠方法模式的核心,含有與應(yīng)用緊密相關(guān)的商業(yè)邏輯。工廠類在客戶端的直接調(diào)用下創(chuàng)建產(chǎn)品對(duì)象,它往往由一個(gè)具體Java類實(shí)現(xiàn)。
抽象產(chǎn)品(Product)角色:擔(dān)任這個(gè)角色的類是工廠方法模式所創(chuàng)建的對(duì)象的父類,或它們共同擁有的接口。抽象產(chǎn)品角色可以用一個(gè)Java接口或者Java抽象類實(shí)現(xiàn)。
具體產(chǎn)品(Concrete Product)角色:工廠方法模式所創(chuàng)建的任何對(duì)象都是這個(gè)角色的實(shí)例,具體產(chǎn)品角色由一個(gè)具體Java 類實(shí)現(xiàn)。
abstract class Fruit {} class Apple extends Fruit {} class Banana extends Fruit {} class FruitFactory { public static Fruit newInstance(Class extends Fruit> clz) { try { return clz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }
每個(gè)工廠類可以有多于一個(gè)的工廠方法,分別負(fù)責(zé)創(chuàng)建不同的產(chǎn)品對(duì)象。比如java.text.DateFormat類是其子類的工廠類,而它就提供了多個(gè)靜態(tài)工廠方法。
在有些情況下,工廠角色可以由抽象產(chǎn)品角色扮演。典型的應(yīng)用就是java.text.DateFormat類,一個(gè)抽象產(chǎn)品類同時(shí)是子類的工廠。
如果抽象產(chǎn)品角色已經(jīng)被忽略,而工廠角色就可以與具體產(chǎn)品角色合并。換言之,一個(gè)產(chǎn)品類為自身的工廠。
class ConcreteProduct { public static ConcreteProduct factory() { return new ConcreteProduct(); } }2.1.3 簡(jiǎn)單工廠模式與其他模式的關(guān)系
單例模式使用了簡(jiǎn)單工廠模式。換言之,單例類具有一個(gè)靜態(tài)工廠方法提供自身的實(shí)例。
單例模式并不是簡(jiǎn)單工廠模式的退化情形,單例模式要求單例類的構(gòu)造方法是私有的,從而客戶端不能直接將之實(shí)例化,而必須通過這個(gè)靜態(tài)工廠方法將之實(shí)例化
單例類自身是自己的工廠角色。換言之,單例類自己負(fù)責(zé)創(chuàng)建自身的實(shí)例。
單例類使用一個(gè)靜態(tài)的屬性存儲(chǔ)自己的惟一實(shí)例 ,工廠方法永遠(yuǎn)僅提供這一個(gè)實(shí)例。
多例模式是對(duì)單例模式的推廣。多例模式與單例模式的共同之處在于它們都禁止外界直接將之實(shí)例化,同時(shí)通過靜態(tài)工廠方法向外界提供循環(huán)使用的自身的實(shí)例。它們的不同在于單例模式僅有一個(gè)實(shí)例,而多例模式則可以有多個(gè)實(shí)例。
多例模式往往具有一個(gè)聚集屬性,通過向這個(gè)聚集屬性登記已經(jīng)創(chuàng)建過的實(shí)例達(dá)到循環(huán)使用實(shí)例的目的。一般而言,一個(gè)典型的多例類具有某種內(nèi)部狀態(tài),這個(gè)內(nèi)部狀態(tài)可以用來區(qū)分各個(gè)實(shí)例;而對(duì)應(yīng)于每一個(gè)內(nèi)部狀態(tài),都只有一個(gè)實(shí)例存在。
根據(jù)外界傳入的參量,工廠方法可以查詢自己的登記聚集,如果具有這個(gè)狀態(tài)的實(shí)例已經(jīng)存在,就直接將這個(gè)實(shí)例提供給外界;反之,就首先創(chuàng)建一個(gè)新的滿足要求的實(shí)例,將之登記到聚集中,然后再提供給客戶端。
單例和多例模式使用了一個(gè)屬性或者聚集屬性來登記所創(chuàng)建的產(chǎn)品對(duì)象, 以便可以通過查詢這個(gè)屬性或者聚集屬性找到和共享已經(jīng)創(chuàng)建了的產(chǎn)品對(duì)象。這就是備忘錄模式的應(yīng)用。
簡(jiǎn)單工廠模式所創(chuàng)建的對(duì)象往往屬于一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu),這個(gè)等級(jí)結(jié)構(gòu)可以是MVC模式中的視圖(View);而工廠角色本身可以是控制器(Controller)。一個(gè)MVC 模式可以有一個(gè)控制器和多個(gè)視圖,如上圖所示。
上圖中的Controller(控制器)也就是工廠角色,它負(fù)責(zé)創(chuàng)建產(chǎn)品View(視圖)。
如果系統(tǒng)需要有多個(gè)控制器參與這個(gè)過程的話,簡(jiǎn)單工廠模式就不適用了,應(yīng)當(dāng)考慮使用工廠方法模式。
2.1.4 簡(jiǎn)單工廠模式的優(yōu)點(diǎn)和缺點(diǎn)工廠類含有必要的判斷邏輯,可以決定在什么時(shí)候創(chuàng)建哪一個(gè)產(chǎn)品類的實(shí)例,客戶端可以免除直接創(chuàng)建產(chǎn)品對(duì)象的責(zé)任,而僅僅“消費(fèi)”產(chǎn)品;簡(jiǎn)單工廠模式通過這種做法實(shí)現(xiàn)了對(duì)責(zé)任的分割,它提供了專門的工廠類用于創(chuàng)建對(duì)象。
客戶端無需知道所創(chuàng)建的具體產(chǎn)品類的類名,只需要知道具體產(chǎn)品類所對(duì)應(yīng)的參數(shù)即可,對(duì)于一些復(fù)雜的類名,通過簡(jiǎn)單工廠模式可以減少使用者的記憶量。
通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產(chǎn)品類,在一定程度上提高了系統(tǒng)的靈活性。
由于工廠類集中了所有產(chǎn)品創(chuàng)建邏輯,一旦不能正常工作,整個(gè)系統(tǒng)都要受到影響。
使用簡(jiǎn)單工廠模式將會(huì)增加系統(tǒng)中類的個(gè)數(shù),在一定程序上增加了系統(tǒng)的復(fù)雜度和理解難度。
系統(tǒng)擴(kuò)展困難,一旦添加新產(chǎn)品就不得不修改工廠邏輯,在產(chǎn)品類型較多時(shí),有可能造成工廠邏輯過于復(fù)雜,不利于系統(tǒng)的擴(kuò)展和維護(hù)。
簡(jiǎn)單工廠模式由于使用了靜態(tài)工廠方法,造成工廠角色無法形成基于繼承的等級(jí)結(jié)構(gòu)。
工廠類負(fù)責(zé)創(chuàng)建的對(duì)象比較少,由于創(chuàng)建的對(duì)象較少,不會(huì)造成工廠方法中的業(yè)務(wù)邏輯太過復(fù)雜。
客戶端只知道傳入工廠類的參數(shù),對(duì)于如何創(chuàng)建對(duì)象不關(guān)心;客戶端既不需要關(guān)心創(chuàng)建細(xì)節(jié),甚至連類名都不需要記住,只需要知道類型所對(duì)應(yīng)的參數(shù)。
2.2 工廠方法模式 2.2.1 工廠方法模式簡(jiǎn)介工廠方法模式是類的創(chuàng)建模式,又叫做虛擬構(gòu)造子模式或多態(tài)性工廠模式。
在工廠方法模式中,核心的工廠類不再負(fù)責(zé)所有的產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建的工作交給子類去做。該核心類成為一個(gè)抽象工廠角色,僅負(fù)責(zé)給出具體工廠子類必須實(shí)現(xiàn)的接口,而不接觸哪一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化這種細(xì)節(jié)。
2.2.2 工廠方法的結(jié)構(gòu)抽象工廠(Creator)角色:擔(dān)任這個(gè)角色的是工廠方法模式的核心,它是與應(yīng)用程序無關(guān)的。任何在模式中創(chuàng)建對(duì)象的工廠類必須實(shí)現(xiàn)這個(gè)接口。在實(shí)際的系統(tǒng)中,這個(gè)角色也常常使用抽象類實(shí)現(xiàn)。
具體工廠(Concrete Creator)角色:擔(dān)任這個(gè)角色的是實(shí)現(xiàn)了抽象工廠接口的具體JAVA類。具體工廠角色含有與業(yè)務(wù)密切相關(guān)的邏輯,并且受到應(yīng)用程序的調(diào)用以創(chuàng)建導(dǎo)出類。
抽象產(chǎn)品(Product)角色:工廠方法模式所創(chuàng)建的對(duì)象的超類,也就是所有產(chǎn)品對(duì)象的共同父類或共同擁有的接口。在實(shí)際的系統(tǒng)中,這個(gè)角色也常常使用抽象類實(shí)現(xiàn)。
具體產(chǎn)品(Concrete Product)角色:這個(gè)角色實(shí)現(xiàn)了抽象產(chǎn)品(Product)角色所聲明的接口,工廠方法模式所創(chuàng)建的每一個(gè)對(duì)象都是某個(gè)具體產(chǎn)品角色的實(shí)例。
abstract class Fruit {} abstract class FruitFactory { public abstract Fruit newInstance(); } class Apple extends Fruit {} class Banana extends Fruit {} class AppleFactory extends FruitFactory { @Override public Fruit newInstance() { return new Apple(); } } class BananaFactory extends FruitFactory { @Override public Fruit newInstance() { return new Banana(); } }2.2.3 工廠方法模式的細(xì)節(jié)
java.util.Collection接口繼承來Iterable接口,所有其子類都必須實(shí)現(xiàn)Iterator
抽象工廠角色和抽象產(chǎn)品角色都可以選擇由Java接口或者Java抽象類來實(shí)現(xiàn)。
如果具體工廠角色由共同的邏輯,那么這些共同的邏輯就可以向上移動(dòng)到抽象工廠角色中,這也意味著抽象工廠角色應(yīng)該由抽象類實(shí)現(xiàn);反之就應(yīng)當(dāng)由接口實(shí)現(xiàn)。
抽象工廠角色可以規(guī)定出多于一個(gè)的工廠方法,從而使具體工廠角色實(shí)現(xiàn)這些不同的工廠方法。
隱藏細(xì)節(jié):在工廠方法模式中,工廠方法用來創(chuàng)建客戶所需要的產(chǎn)品,同時(shí)還向客戶隱藏了哪種具體產(chǎn)品類將被實(shí)例化這一細(xì)節(jié),用戶只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無須關(guān)心創(chuàng)建細(xì)節(jié),甚至無須知道具體產(chǎn)品類的類名。
多態(tài)性設(shè)計(jì):工廠方法模式之所以又被稱為多態(tài)工廠模式,是因?yàn)樗械木唧w工廠類都具有同一抽象父類。基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計(jì)是工廠方法模式的關(guān)鍵,它能夠使工廠可以自主確定創(chuàng)建何種產(chǎn)品對(duì)象,而如何創(chuàng)建這個(gè)對(duì)象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。
完全符合開閉原則:在系統(tǒng)中加入新產(chǎn)品時(shí),無須修改抽象工廠和抽象產(chǎn)品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產(chǎn)品,而只要添加一個(gè)具體工廠和具體產(chǎn)品就可以了。
類數(shù)量太多:在添加新產(chǎn)品時(shí),需要編寫新的具體產(chǎn)品類,而且還要提供與之對(duì)應(yīng)的具體工廠類,系統(tǒng)中類的個(gè)數(shù)將成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,有更多的類需要編譯和運(yùn)行,會(huì)給系統(tǒng)帶來一些額外的開銷。
系統(tǒng)的抽象性和復(fù)雜性:由于考慮到系統(tǒng)的可擴(kuò)展性,需要引入抽象層,在客戶端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度,且在實(shí)現(xiàn)時(shí)可能需要用到DOM、反射等技術(shù),增加了系統(tǒng)的實(shí)現(xiàn)難度。
一個(gè)類不知道它所需要的對(duì)象的類:在工廠方法模式中,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對(duì)應(yīng)的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠類創(chuàng)建;客戶端需要知道創(chuàng)建具體產(chǎn)品的工廠類。
一個(gè)類通過其子類來指定創(chuàng)建哪個(gè)對(duì)象:在工廠方法模式中,對(duì)于抽象工廠類只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類來確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則,在程序運(yùn)行時(shí),子類對(duì)象將覆蓋父類對(duì)象,從而使得系統(tǒng)更容易擴(kuò)展。
動(dòng)態(tài)指定:將創(chuàng)建對(duì)象的任務(wù)委托給多個(gè)工廠子類中的某一個(gè),客戶端在使用時(shí)可以無須關(guān)心是哪一個(gè)工廠子類創(chuàng)建產(chǎn)品子類,需要時(shí)再動(dòng)態(tài)指定,可將具體工廠類的類名存儲(chǔ)在配置文件或數(shù)據(jù)庫中。
2.2.4 工廠方法模式與其他模式工廠方法模式和簡(jiǎn)單工廠模式在結(jié)構(gòu)上的不同很明顯。工廠方法模式的核心是一個(gè)抽象工廠類,而簡(jiǎn)單工廠模式把核心放在一個(gè)具體類上。
如果系統(tǒng)需要加入一個(gè)新的導(dǎo)出類型,那么所需要的就是向系統(tǒng)中加入一個(gè)這個(gè)導(dǎo)出類以及所對(duì)應(yīng)的工廠類。沒有必要修改客戶端,也沒有必要修改抽象工廠角色或者其他已有的具體工廠角色。對(duì)于增加新的導(dǎo)出類型而言,這個(gè)系統(tǒng)完全支持“開-閉原則”。
工廠方法模式常常與模版方法模式一起聯(lián)合使用。原因其實(shí)不難理解:第一,兩個(gè)模式都是基于方法的,工廠方法模式是基于多態(tài)性的工廠方法的,而模版方法模式是基于模版方法和基本方法的;第二,兩個(gè)模式都將具體工作交給子類。工廠方法模式將創(chuàng)建工作推延給子類,模版方法模式將剩余邏輯交給子類。
工廠方法模式總是涉及到兩個(gè)等級(jí)結(jié)構(gòu)中的對(duì)象,而這兩個(gè)等級(jí)結(jié)構(gòu)可以分別是MVC中的控制器和試圖。一個(gè)MVC模式可以有多個(gè)控制器和多個(gè)視圖。
如果系統(tǒng)內(nèi)只需要一個(gè)控制器,那么可以簡(jiǎn)化為簡(jiǎn)單工廠模式。
享元模式使用了帶有循環(huán)邏輯的工廠方法。
2.3 抽象工廠模式 2.3.1 抽象工廠模式簡(jiǎn)介抽象工廠模式是所有形態(tài)的工廠模式中最為抽象和具有一般性的形態(tài)。
“抽象”來自“抽象產(chǎn)品角色”,“抽象工廠”就是抽象產(chǎn)品角色的工廠。
抽象工廠模式與工廠方法模式最大的區(qū)別在于,工廠方法模式針對(duì)的是一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu),而抽象工廠模式則需要面對(duì)多個(gè)產(chǎn)品等級(jí)結(jié)構(gòu)。
2.3.2 抽象工廠方式結(jié)構(gòu)抽象工廠(Creator)角色:擔(dān)任這個(gè)角色的是抽象方法模式的核心,它是與應(yīng)用程序無關(guān)的。
具體工廠(Concrete Creator)角色:具體工廠角色含有與業(yè)務(wù)密切相關(guān)的邏輯,并且受到應(yīng)用程序的調(diào)用以創(chuàng)建導(dǎo)出類。
抽象產(chǎn)品(Product)角色:抽象方法模式所創(chuàng)建的對(duì)象的超類,也就是所有產(chǎn)品對(duì)象的共同父類或共同擁有的接口。
具體產(chǎn)品(Concrete Product)角色:抽象工廠模式所創(chuàng)建的每一個(gè)對(duì)象都是某個(gè)具體產(chǎn)品角色的實(shí)例。
2.3.3 抽象工廠方式細(xì)節(jié)一個(gè)系統(tǒng)不應(yīng)當(dāng)依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、組合和表達(dá)的細(xì)節(jié)。這對(duì)于所有形態(tài)的工廠模式都是重要的;
一個(gè)系統(tǒng)的產(chǎn)品有多于一個(gè)的產(chǎn)品族,而系統(tǒng)只消費(fèi)其中某一族的產(chǎn)品;
同屬于同一個(gè)產(chǎn)品族的產(chǎn)品是在一起使用的,這一約束必須要在系統(tǒng)的設(shè)計(jì)中體現(xiàn)出來;
系統(tǒng)提供一個(gè)產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于實(shí)現(xiàn)。
隔離了具體類的生成,使得用戶不需要知道什么被創(chuàng)建了。
當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能夠保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。
抽象工廠的接口確定了可以被創(chuàng)建的產(chǎn)品集合,所以難以擴(kuò)展抽象工廠以生成新種類的產(chǎn)品。
2.3.4 三種工廠模式總結(jié)下面例子中,手機(jī)、電腦是抽象產(chǎn)品,蘋果、三星等是工廠。
抽象產(chǎn)品叫手機(jī)
具體產(chǎn)品是蘋果手機(jī)、三星手機(jī)
工廠有一個(gè)生產(chǎn)手機(jī)的方法,可以根據(jù)傳入品牌是蘋果還是三星決定生產(chǎn)哪個(gè)品牌的手機(jī)
抽象產(chǎn)品叫手機(jī)
具體產(chǎn)品是蘋果手機(jī)、三星手機(jī)
抽象工廠叫手機(jī)工廠
具體工廠是蘋果手機(jī)工廠和三星手機(jī)工廠,分別生產(chǎn)蘋果手機(jī)和三星手機(jī)
抽象產(chǎn)品叫手機(jī)、電腦
具體產(chǎn)品是蘋果手機(jī)、蘋果電腦、三星手機(jī)、三星電腦
抽象工廠叫手機(jī)電腦工廠,有兩個(gè)方法分別是生產(chǎn)手機(jī)和生產(chǎn)電腦
具體工廠是蘋果工廠和三星工廠,蘋果工廠的兩個(gè)方法分別生產(chǎn)蘋果手機(jī)和蘋果電腦,三星工廠的兩個(gè)方法分別生產(chǎn)三星手機(jī)和三星電腦
2.4 單例模式單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
2.4.1 單例模式細(xì)節(jié)私有化構(gòu)造方法!
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。
主要解決一個(gè)全局使用的類頻繁地創(chuàng)建與銷毀的問題。
屬性文件
Java.lang.Runtime對(duì)象
在內(nèi)存中只有一個(gè)實(shí)例,減少內(nèi)存開支,特別是一個(gè)對(duì)象需要頻繁地創(chuàng)建銷毀時(shí)。
單例模式可以避免對(duì)資源的多重占用,例如一個(gè)寫文件操作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫操作。
單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源訪問,例如,可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。
由于私有化了構(gòu)造方法,所以不能繼承
與單一職責(zé)原則沖突,一個(gè)類應(yīng)該只關(guān)心內(nèi)部邏輯,而不關(guān)心外面怎么樣來實(shí)例化。
特別要注意單例對(duì)象如果持有Context,那么容易引發(fā)內(nèi)存泄漏,此時(shí)需要注意傳遞給單例對(duì)象的context,最好是Application Context
2.4.2 餓漢式與懶漢式加載類的時(shí)候比較慢
運(yùn)行時(shí)獲得對(duì)象的速度比較快
它從加載到應(yīng)用結(jié)束會(huì)一直占用資源。
class EagerSingleton { // 創(chuàng)建單例類對(duì)象 private static EagerSingleton instance = new EagerSingleton(); // 構(gòu)造方法私有化 private EagerSingleton() {} // 獲取可用對(duì)象 public static EagerSingleton getInstance() { return instance; } }
是運(yùn)行時(shí)獲得對(duì)象的速度比較慢
加載類的時(shí)候比較快
它在整個(gè)應(yīng)用的生命周期只有一部分時(shí)間在占用資源。
class LazySingleton { // 聲明單例類對(duì)象 private static LazySingleton instance; // 構(gòu)造方法私有化 private LazySingleton() {} // 獲取可用對(duì)象 public static synchronized LazySingleton getInstance() { if(null == instance) { instance = new LazySingleton(); } return instance; } }2.4.3 懶漢式與雙重檢查成例
由于2.4.2懶漢式代碼中,直接對(duì)整個(gè)getInstance()方法進(jìn)行了同步處理,可能會(huì)導(dǎo)致一些性能問題,于是有了下面的改進(jìn)方法,通過雙重檢查和同步代碼塊的形式來處理懶漢式的并發(fā)問題。但要注意的是,這在Java語言中可能是問題的,之所以是可能有問題,是因?yàn)椴煌琂ava版本的內(nèi)存模型不同。
在第一次檢查時(shí),可能會(huì)有多個(gè)線程同時(shí)到達(dá)(1)處。假設(shè)線程1和線程2都到達(dá)(1)進(jìn)行第一次檢查,此時(shí)instance為null,兩個(gè)線程都通過第一次檢查
然后由于同步代碼塊加鎖,只能有一個(gè)線程獲取鎖。線程1獲取鎖并向下繼續(xù)執(zhí)行,此時(shí)instance仍然為null,于是執(zhí)行(5)初始化instance = new Singleton(),然后線程1執(zhí)行完畢釋放鎖。
然后線程2獲取鎖,此時(shí)第二次檢查判斷instance不為null,所以線程2不會(huì)進(jìn)行初始化,直接退出,返回已經(jīng)初始化好的instance。
以上步驟聽起來是沒有問題的,但問題出在instance = new Singleton()這一句話并不是原子操作!
class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() throws Exception { if(null == instance) { // (1)第一次檢查 // (2)這里會(huì)有多個(gè)線程同時(shí)到達(dá) synchronized(Singleton.class) { // 同步代碼塊加鎖 // (3)此處只能是單線程 if (null == instance) { // (4)第二次檢查 instance = new Singleton(); // (5)初始化instance } } } return instance; } }
為展示問題出現(xiàn)的原因,假設(shè)代碼行instance =new Singleton();執(zhí)行了下列偽代碼:
mem = allocate(); // (1)為單例對(duì)象分配內(nèi)存空間. instance = mem; // (2)注意,instance引用現(xiàn)在已經(jīng)不是null,但還未初始化 ctorSingleton(instance); // (3)為單例對(duì)象通過instance調(diào)用構(gòu)造函數(shù)
上述偽代碼中,執(zhí)行的順序可能是(1)(3)(2),此時(shí)不會(huì)導(dǎo)致上述問題;但如果(1)(2)(3)的執(zhí)行過程,則可能在線程1執(zhí)行到(2)時(shí),CPU開始執(zhí)行線程2,此時(shí)恰好線程2執(zhí)行到第一次檢查,獲取到的是一個(gè)不為null但尚未初始化的值,此時(shí)程序會(huì)拋出錯(cuò)誤。
在高版本的JDK中,使用volatile關(guān)鍵字可以保證不會(huì)產(chǎn)生上述問題。被volatile所修飾的變量的值不會(huì)被本地線程緩存,所有對(duì)該變量的讀寫都是直接操作共享內(nèi)存來實(shí)現(xiàn),從而確保多個(gè)線程能正確的處理該變量。
該關(guān)鍵字可能會(huì)屏蔽掉虛擬機(jī)中的一些代碼優(yōu)化,所以其運(yùn)行效率可能不是很高。
class Singleton { private static volatile Singleton instance; }
class Singleton { private Singleton() {} private static class Holder { static Singleton instance = new Singleton(); } public static Singleton getInstance() { // 外圍類能直接訪問內(nèi)部類(不管是否是靜態(tài)的)的私有變量 return Holder.instance; } }
單例模式與雙重檢測(cè)
雙重檢查的缺陷
用happen-before規(guī)則重新審視DCL
2.5 多例模式多例模式實(shí)際上就是單例模式的推廣,多例類可以有多個(gè)實(shí)例,多例類必須自己創(chuàng)建、管理自己的實(shí)例,并向外界提供自己的實(shí)例。
多例模式分為有上限多例類和無上限多例類,無上限多例類要通過集合來實(shí)現(xiàn)。
建造者模式(Builder Pattern)使用多個(gè)簡(jiǎn)單的對(duì)象一步一步構(gòu)建成一個(gè)復(fù)雜的對(duì)象。它提供了一種創(chuàng)建對(duì)象的最佳方式。
2.6.1 建造者結(jié)構(gòu)Builder(抽象建造者):可以是一個(gè)抽象類或一個(gè)接口,規(guī)范產(chǎn)品對(duì)象的各個(gè)組成部分的建造。
ConcreteBuilder(具體建造者):它實(shí)現(xiàn)了Builder接口,給出一步一步的創(chuàng)建產(chǎn)品實(shí)例的操作,然后提供一個(gè)方法返回創(chuàng)建好的復(fù)雜產(chǎn)品對(duì)象。
Product(產(chǎn)品角色):如果是單個(gè)產(chǎn)品類,那么就是一個(gè)具體的產(chǎn)品;如果是多個(gè)產(chǎn)品類,那么就是一個(gè)抽象的類或接口。
ConcreteProduct(具體產(chǎn)品):當(dāng)多個(gè)產(chǎn)品類時(shí),繼承抽象Product,也就是具體的要建造的復(fù)雜對(duì)象。值得注意的是,這些產(chǎn)品類不一定會(huì)有共同的接口。
Director(指揮者):它復(fù)雜安排復(fù)雜對(duì)象的建造次序,指揮者與抽象建造者之間存在關(guān)聯(lián)關(guān)系,可以在Director的方法中調(diào)用建造者對(duì)象的部件構(gòu)造與裝配方法,完成建造復(fù)雜對(duì)象的任務(wù)。
2.6.2 建造者模式細(xì)節(jié)一個(gè)產(chǎn)品通常有不同的組成成分作為產(chǎn)品的零件,不同的產(chǎn)品可以有不同的零件,建造產(chǎn)品的過程是建造零件的過程。建造者模式將產(chǎn)品的結(jié)構(gòu)和產(chǎn)品的零件建造過程對(duì)外隱藏起來,把對(duì)建造過程進(jìn)行指揮的責(zé)任和具體建造零件的責(zé)任分割開來,達(dá)到責(zé)任劃分和封裝的目的。
主要解決在開發(fā)過程中,有時(shí)需要?jiǎng)?chuàng)建一個(gè)復(fù)雜對(duì)象,通常由多個(gè)部分的子對(duì)象構(gòu)成;由于復(fù)雜對(duì)象的多樣性,這個(gè)復(fù)雜對(duì)象的各個(gè)部分經(jīng)常面臨著劇烈的變化,但是將它們組合在一起的算法需要保持穩(wěn)定。
肯德基的產(chǎn)品很多,需要組成“套餐”。
Java的StringBuilder
省略抽象建造者:如果只需要一個(gè)具體建造者,則可以省略抽象建造者。
省略指揮者:可以在具體建造者里邊直接構(gòu)造具體產(chǎn)品。
合并具體建造者和具體產(chǎn)品:在產(chǎn)品本身就是自己的建造者。
良好的封裝性
具體建造類之間獨(dú)立,擴(kuò)展性好
如果產(chǎn)品比較多,可能會(huì)有很多的建造類。
2.6.3 肯德基套餐案例public class Waiter { public static void main(String[] args) { KFCBuilder builder = new MexicanTwisterBuilder(); builder.buildBeverage(); builder.buildHamburger(); builder.buildSnack(); KFCCombo combo = builder.getCombo(); } } // 套餐接口 abstract class KFCCombo { private String hamburger; private String beverage; private String snack; // getters & setters } // 墨西哥雞肉卷套餐 class MexicanTwisterCombo extends KFCCombo {} // Builder接口 interface KFCBuilder { void buildHamburger(); void buildBeverage(); void buildSnack(); KFCCombo getCombo(); } class MexicanTwisterBuilder implements KFCBuilder { private KFCCombo combo = new MexicanTwisterCombo(); @Override public void buildHamburger() { combo.setHamburger("Mexican Twister"); } @Override public void buildBeverage() { combo.setBeverage("Pepsi Cola"); } @Override public void buildSnack() { combo.setSnack("Hot Wings"); } @Override public KFCCombo getCombo() { return combo; } }2.6.4 builder內(nèi)部類
如果一個(gè)類有很多屬性,此時(shí)為此類寫一個(gè)Builder內(nèi)部類,來輔助建造該類。
class Phone { private String screen; private String camera; private String cpu; private String battery; public static Builder builder() { return new Builder(); } public static class Builder { private Phone phone = new Phone(); public Builder screen(String screen) { phone.screen = screen; return this; } public Builder camera(String camera) { phone.camera = camera; return this; } public Builder cpu(String cpu) { phone.cpu = cpu; return this; } public Builder battery(String battery) { phone.battery = battery; return this; } public Phone build() { return phone; } } }2.6.5 與其他模式的關(guān)系
抽象工廠模式實(shí)現(xiàn)對(duì)產(chǎn)品家族的創(chuàng)建,一個(gè)產(chǎn)品家族是這樣的一系列產(chǎn)品:具有不同分類維度的產(chǎn)品組合,采用抽象工廠模式則是不需要關(guān)心構(gòu)建過程,只關(guān)心什么產(chǎn)品由什么工廠生產(chǎn)即可。
而建造者模式則是要求按照規(guī)定建造產(chǎn)品,它的主要目的是通過組裝零配件而產(chǎn)生一個(gè)新產(chǎn)品。
換言之,抽象工廠模式在更加具體的維度上,而建造模式在一個(gè)更加宏觀的維度上。
事實(shí)上建造模式是策略模式的一種特殊情況,這兩種模式的卻別在于用意不同。
建造模式適應(yīng)于為客戶端一點(diǎn)一點(diǎn)地建造新的對(duì)象。
策略模式的目的是為算法提供抽象的接口。
2.7 原始模型模式原始模型模式通過給一個(gè)原型對(duì)象來指明所要?jiǎng)?chuàng)建的對(duì)象的類型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建出更多同類型的對(duì)象。
2.7.1 原型模式結(jié)構(gòu)
這種模式涉及到三個(gè)角色:
客戶(Client)角色:客戶類提出創(chuàng)建對(duì)象的請(qǐng)求
抽象原型(Prototype)角色:這是一個(gè)抽象角色,此角色給出所以的具體原型類所需的接口。
具體原型(Concrete Prototype):被復(fù)制的對(duì)象。
2.7.2 原型模式細(xì)節(jié)用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過拷貝這些原型創(chuàng)建新的對(duì)象。
創(chuàng)建新對(duì)象成本較大(例如初始化時(shí)間長(zhǎng),占用CPU多或占太多網(wǎng)絡(luò)資源),新對(duì)象可以通過復(fù)制已有對(duì)象來獲得,如果相似對(duì)象,則可以對(duì)其成員變量稍作修改。
系統(tǒng)要保存對(duì)象的狀態(tài),而對(duì)象的狀態(tài)很小。
需要避免使用分層次的工廠類來創(chuàng)建分層次的對(duì)象,并且類的實(shí)例對(duì)象只有一個(gè)或很少的組合狀態(tài),通過復(fù)制原型對(duì)象得到新實(shí)例可以比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便。
當(dāng)創(chuàng)建對(duì)象的實(shí)例較為復(fù)雜的時(shí)候,使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過程,通過復(fù)制一個(gè)已有的實(shí)例可以提高實(shí)例的創(chuàng)建效率。
擴(kuò)展性好,由于原型模式提供了抽象原型類,在客戶端針對(duì)抽象原型類進(jìn)行編程,而將具體原型類寫到配置文件中,增減或減少產(chǎn)品對(duì)原有系統(tǒng)都沒有影響。
原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級(jí)結(jié)構(gòu)相同的工廠等級(jí)結(jié)構(gòu),而原型模式不需要這樣,圓形模式中產(chǎn)品的復(fù)制是通過封裝在類中的克隆方法實(shí)現(xiàn)的,無需專門的工廠類來創(chuàng)建產(chǎn)品。
可以使用深克隆方式保存對(duì)象的狀態(tài),使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來,以便在需要的時(shí)候使用(例如恢復(fù)到歷史某一狀態(tài)),可輔助實(shí)現(xiàn)撤銷操作。
需要為每一個(gè)類配置一個(gè)克隆方法,而且該克隆方法位于類的內(nèi)部,當(dāng)對(duì)已有類進(jìn)行改造的時(shí)候,需要修改代碼,違反了開閉原則。
在實(shí)現(xiàn)深克隆時(shí)需要編寫較為復(fù)雜的代碼,而且當(dāng)對(duì)象之間存在多重簽到引用時(shí),為了實(shí)現(xiàn)深克隆,每一層對(duì)象對(duì)應(yīng)的類都必須支持深克隆,實(shí)現(xiàn)起來會(huì)比較麻煩。
2.7.3 Java的Cloneclone()方法返回的對(duì)象叫做原始對(duì)象的克隆體。一個(gè)克隆對(duì)象的基本特性必須是:
a.clone()!=a,這也就意味著克隆對(duì)象和原始對(duì)象在java中是兩個(gè)不同的對(duì)象。
a.clone().getClass == a.getClass(),克隆對(duì)象與原對(duì)象類型相同
a.clone.equals(a),也就是說克隆對(duì)象完完全全是原始對(duì)象的一個(gè)拷貝。此條件是非必需的。
Object類沒有實(shí)現(xiàn)該接口,所以用戶如果沒有主動(dòng)實(shí)現(xiàn)該接口時(shí),調(diào)用clone()方法會(huì)報(bào)錯(cuò)CloneNotSupportedException。
實(shí)現(xiàn)Cloneable接口,這是步驟的關(guān)鍵之處。
重寫clone()方法,并聲明為public,因?yàn)?b>Object的該方法是protected的。
調(diào)用super.clone()來獲取新的克隆對(duì)象。在運(yùn)行時(shí)刻,Object中的clone()識(shí)別出你要復(fù)制的是哪一個(gè)對(duì)象,然后為此對(duì)象分配空間,并進(jìn)行對(duì)象的復(fù)制,將原始對(duì)象的內(nèi)容一一復(fù)制到新對(duì)象的存儲(chǔ)空間中。
class A implements Cloneable { @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }2.7.4 深復(fù)制和淺復(fù)制
被復(fù)制對(duì)象的所有變量都含有與原來的對(duì)象相同的值,而所有的對(duì)其他對(duì)象的引用仍然指向原來的對(duì)象。換言之,淺復(fù)制僅僅復(fù)制所考慮的對(duì)象,而不復(fù)制它所引用的對(duì)象。
Object.clone()是淺復(fù)制。
被復(fù)制對(duì)象的所有變量都含有與原來的對(duì)象相同的值,除去那些引用其他對(duì)象的變量。那些引用其他對(duì)象的變量將指向被復(fù)制過的新對(duì)象,而不再是原有的那些被引用的對(duì)象。換言之,深復(fù)制把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制了一遍。
深復(fù)制要深入到多少層是一個(gè)不易確定到問題,需要特別注意
深復(fù)制的過程中可能出現(xiàn)循環(huán)引用到問題,需要小心處理
把對(duì)象寫到流里的過程是串行化(Serilization)過程,但是在Java程序師圈子里又非常形象地稱為“冷凍”或者“腌咸菜(picking)”
把對(duì)象從流中讀出來的并行化(Deserialization)過程則叫做 “解凍”或者“回鮮(depicking)”過程。
應(yīng)當(dāng)指出的是,寫在流里的是對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于JVM里面,因此“腌咸菜”的只是對(duì)象的一個(gè)拷貝,Java咸菜還可以回鮮。
利用這個(gè)特性,在Java語言里深復(fù)制一個(gè)對(duì)象,可以先使對(duì)象實(shí)現(xiàn)Serializable接口,然后把對(duì)象(實(shí)際上只是對(duì)象的一個(gè)拷貝)寫到一個(gè)流里,再?gòu)牧骼镒x出來,便可以克隆對(duì)象。
這樣做的前提是對(duì)象以及對(duì)象內(nèi)部所有引用到的對(duì)象都是可串行化的,否則,就需要仔細(xì)考察那些不可串行化的對(duì)象可否設(shè)成transient,從而將之排除在復(fù)制過程之外。
public Object deepClone() throws Exception { //將對(duì)象寫到流里 ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this); //從流里讀出來 ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return(oi.readObject()); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/72502.html
摘要:我們分三篇文章來總結(jié)一下設(shè)計(jì)模式在中的應(yīng)用,這是第一篇?jiǎng)?chuàng)建型模式。二提煉設(shè)計(jì)模式的幾個(gè)原則開閉原則模塊應(yīng)對(duì)擴(kuò)展開放,而對(duì)修改關(guān)閉。工廠模式實(shí)現(xiàn)定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類。設(shè)計(jì)模式的第一部分,創(chuàng)建型模式就總結(jié)完了。 我們分三篇文章來總結(jié)一下設(shè)計(jì)模式在PHP中的應(yīng)用,這是第一篇?jiǎng)?chuàng)建型模式。一、設(shè)計(jì)模式簡(jiǎn)介 首先我們來認(rèn)識(shí)一下什么是設(shè)計(jì)模式: 設(shè)計(jì)模式是一套被反復(fù)使...
摘要:抽象工廠目的創(chuàng)建一系列相關(guān)或依賴的對(duì)象,而不指定它們的具體類。這個(gè)模式是一個(gè)真正的設(shè)計(jì)模式,因?yàn)樗裱艘蕾嚪崔D(zhuǎn)原則眾所周知這個(gè)代表了真正的面向?qū)ο蟪绦蛟O(shè)計(jì)。 【搬運(yùn)于GitHub開源項(xiàng)目DesignPatternsPHP】 項(xiàng)目地址:戳我 1、創(chuàng)建型設(shè)計(jì)模式 在軟件工程中,創(chuàng)建型設(shè)計(jì)模式承擔(dān)著對(duì)象創(chuàng)建的職責(zé),嘗試創(chuàng)建適合程序上下文的對(duì)象,對(duì)象創(chuàng)建設(shè)計(jì)模式的產(chǎn)生是由于軟件工程設(shè)計(jì)的問...
摘要:當(dāng)然,除了讓我們顯得更加專業(yè)之外,在自己所學(xué)習(xí)或者工作的項(xiàng)目中,適當(dāng)合理的使用設(shè)計(jì)模式,能夠給項(xiàng)目帶來很大的好處。 簡(jiǎn)單說兩句 本文首發(fā)公眾號(hào)【一名打字員】 對(duì)不住各位老鐵了,年前說好要更幾波JAVA的東西,又偷懶了,沒辦法,在這里用小錘錘偷偷錘了自己幾下。由于工作原因,更新時(shí)間不定,各位老鐵有問題可以私聊我哈。 對(duì)于初學(xué)者或者是正在向中高級(jí)的Java程序猿(打字員)來說,時(shí)刻梳理自己...
摘要:利用工廠方法模式,請(qǐng)求者發(fā)出請(qǐng)求,而不具體創(chuàng)建產(chǎn)品。正是因?yàn)檫@個(gè)原因,使用工廠方法模式可以簡(jiǎn)化復(fù)雜的創(chuàng)建過程,關(guān)鍵就在于它在維持一個(gè)公共接口。 創(chuàng)建型設(shè)計(jì)模式 包括以下五種: 抽象工廠 生成器 工廠方法 原型 單例 我們選擇工廠方法和原型模式作為將用PHP實(shí)現(xiàn)的創(chuàng)建型設(shè)計(jì)的例子工廠方法模式是這5個(gè)設(shè)計(jì)模式中唯一的一種類設(shè)計(jì)模式原型模式屬于對(duì)象類模式,可以使用PHP_clone方法實(shí)...
摘要:維基百科在軟件工程中,創(chuàng)建型設(shè)計(jì)模式是用于解決對(duì)象創(chuàng)建機(jī)制,嘗試在指定場(chǎng)景下使用合理的方式來創(chuàng)建對(duì)象的設(shè)計(jì)模式。維基百科說建造者模式是一種對(duì)象創(chuàng)建軟件設(shè)計(jì)模式,其目的是找到一種解決方案,以解決可伸縮構(gòu)造函數(shù)的反模式。 1.創(chuàng)建型設(shè)計(jì)模式2.結(jié)構(gòu)型設(shè)計(jì)模式3.行為型設(shè)計(jì)模式 創(chuàng)建型設(shè)計(jì)模式 簡(jiǎn)而言之 創(chuàng)建型設(shè)計(jì)模式關(guān)注的是如何實(shí)例化一個(gè)或者一組相關(guān)的對(duì)象。 維基百科 在軟件工程中,創(chuàng)建型...
閱讀 2058·2023-04-26 02:23
閱讀 1797·2021-09-03 10:30
閱讀 1370·2019-08-30 15:43
閱讀 1200·2019-08-29 16:29
閱讀 544·2019-08-29 12:28
閱讀 2343·2019-08-26 12:13
閱讀 2201·2019-08-26 12:01
閱讀 2417·2019-08-26 11:56