? ? ? ?在客戶端魂务,比較傳統(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)工廠方法栏渺。