effective java 第三版 條目1:思考使用靜態(tài)工廠方法而不是構(gòu)造器

? ? ? ?在客戶端魂务,比較傳統(tǒng)的去獲取一個(gè)類的實(shí)例的方式是提供一個(gè)公共的構(gòu)造器星立。但是這里有另一個(gè)應(yīng)該成為每一個(gè)程序員工具包的的一項(xiàng)技術(shù)——一個(gè)類可以提供一個(gè)公共的返回一個(gè)類的實(shí)例的靜態(tài)工廠方法!這里有一個(gè)簡(jiǎn)單的產(chǎn)生一個(gè)Boolean(boolean的包裝類)的例子面褐,這個(gè)方法將一個(gè)原始的boolean轉(zhuǎn)化為一個(gè)Boolean對(duì)象引用:

public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE;}

? ? ? 記住一點(diǎn)偶芍,靜態(tài)工廠方法和設(shè)計(jì)模式中的工廠模式不相同充择。本條目描述的靜態(tài)工廠方法不等于設(shè)計(jì)模式中的工廠模式。

? ? ? 一個(gè)類能夠使用靜態(tài)工廠提供一個(gè)實(shí)例給他的客戶端匪蟀,而不是使用一個(gè)公共構(gòu)造器椎麦。使用靜態(tài)工廠方法既有優(yōu)點(diǎn)也有缺點(diǎn)。

? ? ? 提供靜態(tài)工廠方法的第一個(gè)好處是材彪,不像構(gòu)造器观挎,靜態(tài)工廠方法有他們的名字。如果一個(gè)構(gòu)造器給定的參數(shù)不能描述一個(gè)它將會(huì)返回的對(duì)象的具體含義段化,靜態(tài)工廠方法將是一個(gè)好的選擇嘁捷!我們可以很簡(jiǎn)單地使用一個(gè)方法名來(lái)說(shuō)明這個(gè)靜態(tài)工廠方法將會(huì)返回一個(gè)怎樣的對(duì)象實(shí)例,因而客戶端的代碼將會(huì)很容易去閱讀显熏。

? ? ? 例如雄嚣,一個(gè)將會(huì)返回一個(gè)? 可能最佳的(probably prime)? BigInteger實(shí)例的構(gòu)造器BigInteger(int,int,Random),使用名為BigInteger.probablePrime 的靜態(tài)工廠方法將會(huì)更好地表達(dá)它的含義喘蟆。(這個(gè)方法在java4被添加)

? ? ? 由于java的構(gòu)造器重載機(jī)制缓升,一個(gè)類的構(gòu)造器只能有被給定一個(gè)相同的簽名。程序員們已經(jīng)大約都知道使用不同順序的參數(shù)類型來(lái)擺脫這個(gè)限制(比如(int,double)和(double,int))蕴轨。但真的是一個(gè)非常糟糕的主意港谊。這樣一個(gè)API的使用者將永遠(yuǎn)不會(huì)記住哪個(gè)構(gòu)造器的含義,最后就會(huì)導(dǎo)致調(diào)用了一個(gè)失誤地錯(cuò)誤的構(gòu)造器橙弱。如果沒有文檔歧寺,閱讀使用這種構(gòu)造器的人將不會(huì)知道這個(gè)代碼到底是什么意思!

? ? ?靜態(tài)工廠方法的第二個(gè)好處是棘脐,不像構(gòu)造器斜筐,他們不需要每一次去創(chuàng)建一個(gè)新的對(duì)象,它允許不可變的類(條目17)去使用重構(gòu)的實(shí)例蛀缝,或者去緩存一個(gè)已經(jīng)構(gòu)造過(guò)的實(shí)例奴艾,重復(fù)地分配這些已經(jīng)創(chuàng)建過(guò)的對(duì)象來(lái)避免不必要的復(fù)制對(duì)象。這個(gè)Boolean.valueOf(boolean) 方法就是就是這個(gè)技術(shù)内斯。它從來(lái)不創(chuàng)建對(duì)象蕴潦,這個(gè)技術(shù)與享元模式相似。如果一個(gè)等價(jià)的對(duì)象經(jīng)常被請(qǐng)求俘闯,這種技術(shù)表現(xiàn)非常棒潭苞。尤其是在這個(gè)對(duì)象創(chuàng)建代價(jià)非常高的時(shí)候!

? ? ?在實(shí)例存在的每個(gè)時(shí)刻真朗,重復(fù)調(diào)用靜態(tài)工廠方法返回相同對(duì)象的的能力將保持精準(zhǔn)對(duì)的控制此疹。這樣做的類被稱為instance-controlled。這里有許多原因去寫一個(gè)可控實(shí)例,實(shí)例控制允許一個(gè)類保證它是單例的(條目3)蝗碎,或者不可實(shí)例化(條目4)湖笨。同時(shí),它允許一個(gè)值不可變的類(條目17)保證沒有第二個(gè)與他等價(jià)的實(shí)例存在:a.equals(b)?if?and only?if?a?==?b蹦骑。這是這是享元模式的基礎(chǔ)慈省。枚舉類型(條目34)就提供這樣一個(gè)保證。

? ? ? 靜態(tài)工廠方法的第三個(gè)好處是眠菇,不像構(gòu)造器边败,它可以返回一個(gè)任意返回類型的子類類型的對(duì)象。這給你更大的靈活性在選擇返回對(duì)象的類型上捎废。

? ? ?一個(gè)這種靈活性的應(yīng)用是笑窜,一個(gè)API能夠返回一個(gè)非public修飾的類的對(duì)象。隱藏實(shí)現(xiàn)的類將形成一個(gè)非常緊湊的API,這項(xiàng)技術(shù)把他借給 接口基礎(chǔ)框架(interface-based?frameworks)(條目20)登疗,這些接口使用靜態(tài)工廠方法提供一個(gè)合理的返回類型排截。

在java8之前,接口不能有靜態(tài)方法辐益,通常而言匾寝,對(duì)于一個(gè)名為Type的t接口將會(huì)放入一個(gè)名為Types的不可實(shí)例化的組合類(條目4)。例如荷腊,java 集合框架中接口4/5功能實(shí)現(xiàn),都會(huì)提供一個(gè) 不可修改的的包裝集合類(unmodifiable )急凰,加鎖的包裝集合類(synchronized)女仰,差不多的,所有的實(shí)現(xiàn)都是在一個(gè)不可實(shí)例化的類(java.util.Collections)通過(guò)靜態(tài)工廠方法導(dǎo)出的抡锈,這些返回的對(duì)象的class都是非public的疾忍!

? ? ?集合框架的API比被他導(dǎo)出的4/5的分開的public classes要更小...(The Collections Framework API is much smaller than it would have been?had it?exported?forty-five?separate?public?classes,?one?for?each?convenience?implemen- tation.?床三?一罩?)。它不但是減少了API上的體積撇簿,而且也降低了概念上的體積:這個(gè)概念的數(shù)量和難度將會(huì)讓程序員可以更好地去使用這個(gè)API聂渊。程序員知道返回的對(duì)象的功能已經(jīng)精確地被接口指定,所以這里不需要去為了這些實(shí)現(xiàn)類去閱讀額外的關(guān)于類的文檔四瘫。除此之外汉嗽,使用這樣一個(gè)靜態(tài)工廠方法需要客戶端器關(guān)心這個(gè)由接口返回的對(duì)象而不是實(shí)現(xiàn)類,這通常是一個(gè)好的練習(xí)找蜜。(條目64)

? 自從java8饼暑,接口中不能包含靜態(tài)方法的限制被排除了,因此這里有一個(gè)典型的原因是為去接口提供一個(gè)不可實(shí)例化的同伴類。許多本來(lái)在一個(gè)類中的公共靜態(tài)成員而不是被放在接口中弓叛。記住彰居,無(wú)論如何,講一個(gè)大體量的實(shí)現(xiàn)代碼放在一個(gè)私有的靜態(tài)方法中仍然很有必要撰筷!這是因?yàn)樵趈ava8中需要一個(gè)接口的所有靜態(tài)方法必須是public的陈惰,java9允許私有靜態(tài)方法,單是靜態(tài)字段和靜態(tài)成員類仍然必須是public的闭专!

? 靜態(tài)工廠方法的第四個(gè)好處是奴潘,這個(gè)返回對(duì)象的class屬性可以是變化的從調(diào)用到作為一個(gè)輸入?yún)?shù)的函數(shù)被調(diào)用。任何聲明類型的子類都是被允許的影钉。這個(gè)返回對(duì)象的類從依賴到依賴同樣可以是變化的画髓。(A?fourth?advantage?of?static?factories?is?that?the?class?of?the?returned?object?can?vary?from?call?to?call?as?a?function?of?the?input?parameters.?Any?sub-?type?of?the?declared?return?type?is?permissible.?The?class?of?the?returned?object?can

also vary from release to release.?平委?奈虾?)

? EnumSet類(條目36)沒有公共的構(gòu)造器,只有靜態(tài)工廠廉赔。在OpenJdk實(shí)現(xiàn)中肉微,他們返回了兩個(gè)子類的其中一個(gè)類型的實(shí)例,返回的實(shí)例依賴于這個(gè)潛在的枚舉類型的長(zhǎng)度:如果有64或者更小的元素在枚舉值蜡塌,靜態(tài)工廠方法返回一個(gè)RegularEnumSet實(shí)例碉纳,如果返回枚舉類型有大于65的元素,這個(gè)工廠將會(huì)返回一個(gè)由大數(shù)組支持的JumboEnumSet 實(shí)例馏艾。

? ? 對(duì)于客戶端劳曹,這兩個(gè)實(shí)現(xiàn)類的存在是不可見的。如果RegularEnumSet 導(dǎo)致提供一個(gè)更好的表現(xiàn)對(duì)于更小的枚舉類型琅摩,它你能夠從未來(lái)沒有不良影響的釋放中排除铁孵。相同的,一個(gè)未來(lái)的依賴能夠添加1/3或者1/4EnumSet的實(shí)現(xiàn)如果它提供更好的 表現(xiàn)房资⊥扇埃客戶端不用知道也不用關(guān)新他們從工廠中得到的類的class屬性;他們可以只關(guān)心這些EnumSet是相同的轰异。

? ? ?靜態(tài)工廠方法的第五個(gè)好處是岖沛,這個(gè)返回對(duì)象的class不需要存在當(dāng)這個(gè)類包含的這些方法被重寫(返回匿名類)。許多來(lái)自于service provider frameworks的基礎(chǔ)的靈活的靜態(tài)工廠方法搭独。像JDBC API烫止。一個(gè)服務(wù)提供框架是一個(gè)提供實(shí)現(xiàn)服務(wù)的系統(tǒng),這個(gè)系統(tǒng)使這些實(shí)現(xiàn)在客戶端上可用戳稽,將客戶端從這些實(shí)現(xiàn)中解耦馆蠕。

? ? ?這里有三個(gè)在服務(wù)提供框架中的組件期升,一個(gè)是服務(wù)接口,它提供一個(gè)實(shí)現(xiàn)互躬,一個(gè)是提供者注冊(cè)接口播赁,使用者通常注冊(cè)實(shí)現(xiàn),還有一個(gè)是可能被為了選擇實(shí)現(xiàn)時(shí)被客戶端允許而指定的標(biāo)準(zhǔn)的可通行的API吼渡。如果這個(gè)標(biāo)準(zhǔn)不存在容为,這個(gè)API將返回的默認(rèn)實(shí)現(xiàn)的實(shí)例,或者允許客戶端循環(huán)通過(guò)所有可用的實(shí)例寺酪。這個(gè)服務(wù)service?access API就是 形成服務(wù)提供框架的基礎(chǔ) 的 靈活的 靜態(tài)工廠

一個(gè)可選擇的服務(wù)提供框架的第四個(gè)組件是服務(wù)提供接口,它描述了一個(gè)產(chǎn)生服務(wù)接口實(shí)例的工廠對(duì)象寄雀,在這個(gè)服務(wù)提供接口標(biāo)準(zhǔn)中,實(shí)現(xiàn)被深思熟慮地實(shí)例化(條目32)盒犹。在JDBC這個(gè)問(wèn)題中,Connection 是服務(wù)接口的部分急膀,DriverManager.registerDriver是提供注冊(cè)的API,?DriverManager.getConnection是服務(wù)通行API卓嫂,而Driver 是服務(wù)提供接口。

? ? 這里有許多服務(wù)提供框架模式的變體晨雳,例如行瑞,服務(wù)通行API可以返回一個(gè)更多的服務(wù)接口到客戶端而不是一個(gè)由提供者提供的實(shí)例。這是橋接模式悍募。依賴注入框架(條目5)可以被看做有力的服務(wù)提供者。自從java6開始洋机,java平臺(tái)加入了通用目的服務(wù)提供框架 java.util.ServiceLoader坠宴,所以你不需要通常也不應(yīng)該去寫你自己的服務(wù)提供框架。JDBC沒有使用ServiceLoader(條目59)绷旗,因?yàn)樗萐erviceLoader更早地出現(xiàn)喜鼓。

? ? ? ?僅提供一個(gè)靜態(tài)工廠方法的最主要的限制是這個(gè)類沒有公共和保護(hù)級(jí)別的構(gòu)造器,所以他們不能被繼承衔肢。例如庄岖,想要繼承Collections(非Collection)框架中方便的實(shí)現(xiàn)時(shí)不可能的!單是這個(gè)隱藏也可能是一個(gè)好處角骤,因?yàn)樗膭?lì)程序員多使用組合而不是繼承(條目18)隅忿。同時(shí)這是被要求需要是不可變類型P陌(條目17)

? ? ?而靜態(tài)工廠方法的第二個(gè)缺點(diǎn)是很難去發(fā)現(xiàn)它們。這些方法不像構(gòu)造器那樣存在在API文檔中(比如Collections)背桐,所以很難去指出怎樣通過(guò)靜態(tài)工廠方法而不是一個(gè)構(gòu)造器實(shí)例化一個(gè)類优烧。JavaDoc工具可能將會(huì)有一天注意到靜態(tài)工廠方法,在那時(shí)链峭,你可以減少去關(guān)注類或者接口文檔中的靜態(tài)工廠方法的一些問(wèn)題畦娄。

? ? 這里有一些通常見到的靜態(tài)工廠方法的命名方式:

? ? ①from——一個(gè)有一個(gè)參數(shù)的類型轉(zhuǎn)換方法,它返回一個(gè)這個(gè)類型對(duì)應(yīng)的實(shí)例弊仪。例如:Date d = Date.from(instant)

? ? ②of——一個(gè)有對(duì)重參數(shù)的聚集方法熙卡,它將返回一個(gè)一個(gè)類型的組合的實(shí)例,例如:Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING)

? ?③valueOf——一個(gè)更啰嗦的跟from和of一樣的方法励饵,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

? ?④instance和getInstance——返回一個(gè)由他的參數(shù)描述的實(shí)例驳癌,但是不能說(shuō)著一定會(huì)有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);

? ?⑤create和newInstance——像instance和getInstance方法曲横,單是他們不會(huì)每一次調(diào)用都返回一個(gè)新的實(shí)例喂柒,例如Object newArray = Array.newInstance(classObject, arrayLen)

? ? ⑥getType——像getnstance,但是通常如果這個(gè)工廠方法將返回不同的class禾嫉,Type是工廠方法返回的對(duì)象的類型灾杰。例如:FileStore fs = Files.getFileStore(path)

? ?⑦newType——像newInstance,但是通常如果靜態(tài)工廠方法將返回不同的class熙参,Type是工廠方法返回的對(duì)象的類型艳吠。例如:BufferedReader br = Files.newBufferedReader(path)

? ? ⑧一個(gè)getType和newType的替代選擇,例如:List litany = Collections.list(legacyLitany)

總結(jié)孽椰,靜態(tài)工廠方法和構(gòu)造器都有他們的用處昭娩,這需要花時(shí)間去理解他們相關(guān)的特點(diǎn),通常靜態(tài)工廠方法是更好的黍匾,所以最好在提供一個(gè)構(gòu)造器之后要思考一下是不是可以優(yōu)先提供一個(gè)靜態(tài)工廠方法栏渺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末磕诊,一起剝皮案震驚了整個(gè)濱河市霎终,隨后出現(xiàn)的幾起案子莱褒,更是在濱河造成了極大的恐慌,老刑警劉巖广凸,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異目派,居然都是意外死亡胁赢,警方通過(guò)查閱死者的電腦和手機(jī)智末,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門系馆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人闽寡,你說(shuō)我怎么就攤上這事爷狈。” “怎么了涎永?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惶我。 經(jīng)常有香客問(wèn)我绸贡,道長(zhǎng),這世上最難降的妖魔是什么结洼? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任叉跛,我火速辦了婚禮筷厘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摊溶。我一直安慰自己莫换,他們只是感情好骤铃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布惰爬。 她就那樣靜靜地躺著撕瞧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巩掺。 梳的紋絲不亂的頭發(fā)上硼婿,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天刊殉,我揣著相機(jī)與錄音记焊,去河邊找鬼栓撞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瓢颅,可吹牛的內(nèi)容都是我干的挽懦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼信柿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼渔嚷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起形病,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搀罢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后榔至,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唧取,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枫弟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伊履。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唐瀑。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哄辣,死狀恐怖力穗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情够坐,我是刑警寧澤咆霜,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站脉课,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏倘零。R本人自食惡果不足惜呈驶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一袖瞻、第九天 我趴在偏房一處隱蔽的房頂上張望聋迎。 院中可真熱鬧,春花似錦枣耀、人聲如沸霉晕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)牺堰。三九已至,卻和暖如春颅围,著一層夾襖步出監(jiān)牢的瞬間萌焰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谷浅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扒俯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓一疯,卻偏偏與公主長(zhǎng)得像撼玄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掌猛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容