1. 程式人生 > >防禦式拷貝

防禦式拷貝

假設類的客戶端會盡其所能來破壞這個類的約束條件,因此你必須保護性的設計程式。
demo:

[java]  view plain  copy
  1. import java.util.Date;  
  2.   
  3. public final class Period {  
  4.     private final Date start;  
  5.     private final Date end;  
  6.     public Period(Date start,Date end) {  
  7.         if(start.compareTo(end) > 0){  
  8.             throw
     new IllegalArgumentException(start + " after " + end);  
  9.         }  
  10.         this.start = start;  
  11.         this
    .end = end;  
  12.     }  
  13.       
  14.     public Date start(){  
  15.         return start;  
  16.     }  
  17.       
  18.     public Date end(){  
  19.         return end;  
  20.     }  
  21.     //remainder omitted  
  22. }  
這個類看上去沒有什麼問題,時間是不可改變的。然而Date類本身是可變的。
[java]  view plain  copy
  1. Date start = new Date();  
  2. Date end = new Date();  
  3. Period period = new Period(start, end);  
  4. end.setYear(78);  
  5. System.out.println(period.end());  
為了保護Period例項的內部資訊避免受到修改,導致問題,對於構造器的每個可變引數進行保護性拷貝(defensive copy)是必要的:
[java]  view plain  copy
  1. public Period(Date start,Date end) {  
  2.     this.start = new Date(start.getTime());  
  3.     this.end = new Date(end.getTime());  
  4.     if(this.start.compareTo(this.end) > 0){  
  5.         throw new IllegalArgumentException(this.start + " after " + this.end);  
  6.     }  
  7. }         

保護性拷貝是在檢查引數的有效性之前進行的,並且有效性檢查是針對拷貝之後的物件,而不是原始物件。


對於引數型別可以被不可信任方子類化的引數,請不要使用clone方法進行保護性拷貝。


通過改變Period:

[java]  view plain  copy
  1. Date start = new Date();  
  2. Date end = new Date();  
  3. Period period = new Period(start, end);  
  4. period.end().setYear(98);  
  5. System.out.println(period.end());  
為了防止二次攻擊,可以讓end()返回拷貝物件。
[java]  view plain  copy
  1. public Date end(){  
  2.     return new Date(end.getTime());  
  3. }  

但是這樣讓人寫起來很浮躁,所以還是要有一個必要性的把握。


引數的保護性拷貝不僅僅針對不可變類。每當編寫編寫方法和構造器時,如果他要允許客戶提供的物件進入到內部資料結構中,則有必要考慮一下,客戶提供的物件是否有可能是可變的,我是否能夠容忍這種可變性。特別是你用到list、map之類連線元素時。


在內部元件返回給客戶端的時候,也要考慮是否可以返回一個指向內部引用的資料。或者,不使用拷貝,你也可以返回一個不可變物件。如:Colletions.unmodifiableList(List<? extends T> list)


如果類具有從客戶端得到或者返回到客戶端的可變元件,類就必須保護性的拷貝這些元件。如果拷貝的成本受到限制,並且類信任他的客戶端不會進行修改,或者恰當的修改,那麼就需要在文件中指明客戶端呼叫者的責任(不的修改或者如何有效修改)。
特別是當你的可變元件的生命週期很長,或者會多層傳遞時,隱藏的問題往往暴漏出來就很可怕。