定義
一個(gè)對(duì)象的狀態(tài)在對(duì)象被創(chuàng)建成功之后就不再變化性芬,這就是所謂的不變模式。
不變模式的結(jié)構(gòu)
不變模式可增強(qiáng)對(duì)象的強(qiáng)壯性(robustness)。不變模式允許多個(gè)對(duì)象共享某一個(gè)對(duì)象,降低了對(duì)該對(duì)象進(jìn)行并發(fā)訪問的同步化開銷兔港。如果需要修改一個(gè)不變對(duì)象的狀態(tài),那么就需要建立一個(gè)新的同類型對(duì)象仔拟,并在創(chuàng)建時(shí)將這個(gè)新的狀態(tài)存儲(chǔ)在新對(duì)象里衫樊。
不變模式只涉及到一個(gè)類。一個(gè)類的內(nèi)部狀態(tài)創(chuàng)建后利花,在整個(gè)生命周期都不會(huì)發(fā)生變化時(shí)科侈,這樣的類成為不變類。這種使用不變類的做法叫做不變模式炒事。不變模式有兩種形式:一種是弱不變模式臀栈,另一種是強(qiáng)不變模式。
弱不變模式
一個(gè)類的實(shí)例的狀態(tài)時(shí)不可改變的挠乳;但是這個(gè)類的子類的實(shí)例具有可能會(huì)變化的狀態(tài)权薯,這樣的類符合弱不變的定義。要實(shí)現(xiàn)弱不變模式睡扬,一個(gè)類必須滿足下列條件:
- 第一盟蚣、所考慮的對(duì)象沒有任何方法會(huì)修改對(duì)象的狀態(tài);這樣一來卖怜,當(dāng)對(duì)象的構(gòu)造函數(shù)將對(duì)象的狀態(tài)初始化之后屎开,對(duì)象的狀態(tài)便不再改變。
- 第二马靠、所有屬性都應(yīng)當(dāng)是私有的奄抽。不要聲明任何公開的屬性,以防客戶端對(duì)象直接修改任何的內(nèi)部狀態(tài)甩鳄。
- 第三逞度、這個(gè)對(duì)象所引用到的其他對(duì)象如果是可變對(duì)象的話,必須設(shè)法限制外界對(duì)這些可變對(duì)象的訪問娩贷,以防止外界修改這些對(duì)象第晰。如果可能,應(yīng)當(dāng)盡量在不變對(duì)象內(nèi)部初始化這些被引用的對(duì)象彬祖,而不要在客戶端初始化茁瘦,然后再傳入到不變對(duì)象內(nèi)部來。如果某個(gè)可變對(duì)象必須在客戶端初始化储笑,然后再傳入不變對(duì)象里的話甜熔,就應(yīng)當(dāng)考慮在不變對(duì)象初始化的時(shí)候,將這個(gè)可變對(duì)象復(fù)制一份突倍,而不要使用原來的拷貝腔稀。
弱不變模式的缺點(diǎn)是:
- 第一、一個(gè)弱不變對(duì)象的子對(duì)象可以是可變對(duì)象羽历;換而言之焊虏,一個(gè)弱不變對(duì)象的子對(duì)象可能是可變的。
- 第二秕磷、這個(gè)可變的子對(duì)象可能可以修改父對(duì)象的狀態(tài)诵闭,從而可能會(huì)允許外界修改父對(duì)象的狀態(tài)。
強(qiáng)不變模式
一個(gè)類的實(shí)例不會(huì)改變澎嚣,同時(shí)它的子類的實(shí)例也具有不可變化的狀態(tài)疏尿。這樣的類符合強(qiáng)不變模式。要實(shí)現(xiàn)強(qiáng)不變模式易桃,一個(gè)類必須首先滿足弱不變模式所要求的任何條件褥琐,并且還要滿足下面條件之一:
- 第一、所考慮的類的所有方法都應(yīng)當(dāng)是
final
晤郑,這樣這個(gè)類的子類不能夠置換掉此類的方法敌呈。 - 第二、這個(gè)類本身就是
final
的造寝,那么這個(gè)類就不可能會(huì)有子類磕洪,從而也就不可能有被子類修改的問題。
“不變”和“只讀”的區(qū)別
“不變”(Immutable
)與“只讀”(ReadOnly
)是不同的匹舞。當(dāng)一個(gè)變量是“只讀”時(shí)褐鸥,變量的值不能直接改變,但是可以在其他變量發(fā)生改變時(shí)發(fā)生改變赐稽。
比如叫榕,一個(gè)人的出生年月日是“不變”屬性,而一個(gè)人的年齡便是“只讀”屬性姊舵,而不是“不變”屬性晰绎。隨著時(shí)間的變化,一個(gè)人的年齡會(huì)隨之發(fā)生變化括丁,而人的出生年月日則不會(huì)變化荞下。這就是“不變”和“只讀”的區(qū)別
不變模式在Java中的應(yīng)用
不變模式在Java中最著名的應(yīng)用便是Java.lang.String
類。String
類是一個(gè)強(qiáng)不變類型,在出現(xiàn)如下的語句時(shí):
String a = "test";
String b = "test";
String c = "test";
Java虛擬機(jī)中其實(shí)只會(huì)創(chuàng)建這樣一個(gè)字符串的實(shí)例尖昏,而這三個(gè)String
對(duì)象都在共享這一個(gè)值仰税。
不變模式的優(yōu)點(diǎn)和缺點(diǎn)
不變模式有很明顯的優(yōu)點(diǎn):
- 因?yàn)椴荒苄薷囊粋€(gè)不變對(duì)象的狀態(tài),所以可以避免由此引起的不必要的程序錯(cuò)誤抽诉;換而言之陨簇,一個(gè)不變的對(duì)象要比可變的對(duì)象更加容易維護(hù)。
- 因?yàn)闆]有任何一個(gè)線程能夠修改不變對(duì)象的內(nèi)部狀態(tài)迹淌,一個(gè)不變對(duì)象自動(dòng)就是線程安全的河绽,這樣就可以省掉處理同步化的開銷。一個(gè)不變對(duì)象可以自由的被不同的客戶端共享唉窃。
不變模式的唯一缺點(diǎn)是:
一旦需要修改一個(gè)不變對(duì)象的狀態(tài)耙饰,就只好創(chuàng)建一個(gè)新的同類對(duì)象。在需要頻繁修改不變對(duì)象的環(huán)境里纹份,會(huì)有大量的不變對(duì)象作為中間結(jié)果被創(chuàng)建出來苟跪,再被Java垃圾收集器收集走。這是一種資源上的浪費(fèi)矮嫉。
在設(shè)計(jì)任何一個(gè)類的時(shí)候削咆,應(yīng)當(dāng)慎重考慮其狀態(tài)是否有需要變化的可能性。除非其狀態(tài)有變化的需要蠢笋,不然應(yīng)當(dāng)將它設(shè)計(jì)成不變類拨齐。