當(dāng)我們?cè)谠O(shè)計(jì)一個(gè)不可變類,要注意保證它的組件也是不可變的,因此要進(jìn)行保護(hù)性拷貝。尤其是類的可變組件是來(lái)自于客戶端時(shí),盡管我們可以跟客戶端約定不會(huì)修改這些組件,但是都不如保護(hù)性拷貝來(lái)的保險(xiǎn)。
1.在設(shè)計(jì)不可變類時(shí),不可變組件是首選
使用不可變的類作為我們新設(shè)計(jì)的類的組件,那么從類自身的屬性上就可以確保它是不可變的了。如Instant、LocalTimeDate或ZonedDateTime 就優(yōu)于Date.2.實(shí)在需要使用可變類時(shí),首先應(yīng)該在構(gòu)造器中進(jìn)行保護(hù)性拷貝
如這個(gè)Period 類的例子:
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(start + " after " + end);
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
上面這種沒有保護(hù)性拷貝的構(gòu)造方法,就有可能在初始化之后,被客戶端修改內(nèi)部Date 類的對(duì)象:
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); /
使用保護(hù)性拷貝的構(gòu)造方法如下:
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(this.start + " after " + this.end);
}
可以避免上述問(wèn)題。
這里需要注意的地方是,如果有入?yún)⑿r?yàn),應(yīng)該在對(duì)拷貝后的對(duì)象校驗(yàn),否則在校驗(yàn)前,對(duì)象可能就已經(jīng)被其他程序修改了。
- 3.還需要對(duì)類中可以訪問(wèn)到可變組件的方法進(jìn)行保護(hù)性拷貝
如這里的Date 的get、set 方法。
如果不進(jìn)行保護(hù)性拷貝,那么客戶端還是有可能通過(guò)這些方法更新可變組件,如getEnd().setYear(72).
因此還要注意把這些方法進(jìn)行保護(hù)性拷貝:
// Repaired accessors - make defensive copies of internal fields
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
保護(hù)性拷貝是在設(shè)計(jì)不可變類時(shí),存在可變組件時(shí)的一種技巧。其核心思路是:把不可變類的可變組件隱藏起來(lái),客戶端想訪問(wèn)時(shí),只提供一個(gè)“”替身“”,從而避免客戶端修改到這些組件的狀態(tài)。
讀這部分內(nèi)容時(shí)讓我聯(lián)想起了兩部分內(nèi)容:
一是對(duì)數(shù)組的運(yùn)算,因?yàn)閿?shù)組和列表總是可變的,有時(shí)我們?cè)谟?jì)算時(shí)為了保證一個(gè)數(shù)組的結(jié)構(gòu)和其內(nèi)部對(duì)象的狀態(tài)不變,會(huì)選擇拷貝出一個(gè)新的數(shù)組或列表來(lái)操作。
二是建造者模式,如MsgBuilder。 builder 往往是根據(jù)一個(gè)或多個(gè)對(duì)象來(lái)組裝出自己想要的對(duì)象。如UserMsgBuilder(User user)。UserMsgBuilder 本身不應(yīng)該修改到user 的狀態(tài)。(當(dāng)然這里可能不太需要真的把User 設(shè)計(jì)成不可變類,更多是一種約定)。