item 50 leekyunghee - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

방어적 λ³΅μ‚¬λž€?

μ–΄λ–€ 클래슀의 λ©”μ„œλ“œμ—μ„œ 클래슀의 멀버 λ³€μˆ˜(객체)에 값을 μ“°κ±°λ‚˜ κ°€μ Έμ˜¬ λ•Œ 볡사본을 λ§Œλ“€μ–΄μ„œ get/set 을 μ‚¬μš©ν•˜λŠ” 것이닀.

예λ₯Ό λ“€μ–΄μ„œ get/set λ©”μ„œλ“œμΌ 뿐 λ‹€λ₯Έ λ©”μ„œλ“œλ„ 멀버 λ³€μˆ˜μ— μ ‘κ·Όν•œλ‹€λ©΄ 볡사본을 λ§Œλ“€ ν•„μš”κ°€ μžˆλ‹€.)

  • μ€‘μš”ν•œ 것은 νŒŒλΌλ―Έν„°μ˜ μœ νš¨μ„±μ„ κ²€μ‚¬ν•˜κΈ° 전에 방어적 볡사본을 λ§Œλ“€κ³  이 λ³΅μ‚¬λ³ΈμœΌλ‘œ μœ νš¨μ„±μ„ κ²€μ‚¬ν•΄μ•Όν•œλ‹€.

μ μ‹œμ— 방어적 볡사본을 λ§Œλ“€λΌ

  • μžλ°”λŠ” Cλ‚˜ C++에 λΉ„ν•΄ λ©”λͺ¨λ¦¬ 좩돌 였λ₯˜μ—μ„œ μ•ˆμ „ν•˜λ‹€.
  • μžλ°”λ‘œ μž‘μ„±ν•œ ν΄λž˜μŠ€λŠ” μ‹œμŠ€ν…œμ˜ λ‹€λ₯Έ λΆ€λΆ„μ—μ„œ 무슨 짓을 ν•˜λ“  κ·Έ λΆˆλ³€μ‹ 이 μ§€μΌœμ§„λ‹€.

λ‹€λ₯Έ ν΄λž˜μŠ€λ‘œλΆ€ν„° 침법을 λ§‰κΈ°μœ„ν•΄ λ°©μ–΄μ μœΌλ‘œ ν”„λ‘œκ·Έλž˜λ°ν•΄μ•Ό ν•œλ‹€.

  • 아무리 μžλ°”λΌ 해도 λ‹€λ₯Έ ν΄λž˜μŠ€λ‘œλΆ€ν„° 침범을 μ•„λ¬΄λŸ° λ…Έλ ₯ 없이 λ‹€ 막을 수 μžˆλŠ” 건 μ•„λ‹ˆλ‹€.
  • μ–΄λ–€ 객체든 κ·Έ 객체의 ν—ˆλ½ μ—†μ΄λŠ” μ™ΈλΆ€μ—μ„œ λ‚΄λΆ€λ₯Ό μˆ˜μ •ν•˜λŠ” 일은 λΆˆκ°€λŠ₯ν•˜λ‹€. ν•˜μ§€λ§Œ 주의λ₯Ό κΈ°μšΈμ΄μ§€ μ•ŠμœΌλ©΄ μžκΈ°λ„ λͺ¨λ₯΄κ²Œ λ‚΄λΆ€λ₯Ό μˆ˜μ •ν•˜λ„λ‘ ν—ˆλ½ν•˜λŠ” κ²½μš°κ°€ 생긴닀.

기간을 ν¬ν˜„ν•˜λŠ” 클래슀

  • Dateκ°€ κ°€λ³€μ΄λΌλŠ” 사싀을 μ΄μš©ν•˜λ©΄ λΆˆλ³€μ‹μ„ 깨뜨릴 수 μžˆλ‹€.
// μ½”λ“œ 50-1 기간을 ν‘œν˜„ν•˜λŠ” 클래슀 - λΆˆλ³€μ‹μ„ 지킀지 λͺ»ν–ˆλ‹€. (302-305μͺ½)
public final class Period {
    private final Date start;
    private final Date end;

    /**
     * @param  start μ‹œμž‘ μ‹œκ°
     * @param  end μ’…λ£Œ μ‹œκ°. μ‹œμž‘ μ‹œκ°λ³΄λ‹€ λ’€μ—¬μ•Ό ν•œλ‹€.
     * @throws IllegalArgumentException μ‹œμž‘ μ‹œκ°μ΄ μ’…λ£Œ μ‹œκ°λ³΄λ‹€ λŠ¦μ„ λ•Œ λ°œμƒν•œλ‹€.
     * @throws NullPointerException startλ‚˜ endκ°€ null이면 λ°œμƒν•œλ‹€.
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(
                    start + "κ°€ " + end + "보닀 λŠ¦λ‹€.");
        this.start = start;
        this.end   = end;
    }

    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }

    public String toString() {
        return start + " - " + end;
    }
}

μžλ°” 8 μ΄ν›„λ‘œλŠ” μ‰½κ²Œ ν•΄κ²° κ°€λŠ₯

// μ½”λ“œ 50-2 Period μΈμŠ€ν„΄μŠ€μ˜ λ‚΄λΆ€λ₯Ό κ³΅κ²©ν•΄λ³΄μž. (303μͺ½)
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        end.setYear(78);  // p의 λ‚΄λΆ€λ₯Ό λ³€κ²½ν–ˆλ‹€!
        System.out.println(p);
  • Date λŒ€μ‹  λΆˆλ³€μΈ Instantλ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€. (ν˜Ήμ€ LocalTimeμ΄λ‚˜ ZoneDateTIme을 μ‚¬μš©ν•΄λ„ λœλ‹€)
  • DateλŠ” 낑은 APIμ΄λ‹ˆ μƒˆλ‘œμš΄ μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” 더 이상 ν•˜μš©ν•˜λ©΄ μ•ˆλœλ‹€.
  • ν•˜μ§€λ§Œ μ•žμœΌλ‘œ 쓰지 μ•ŠλŠ”λ‹€κ³  이 λ¬Έμ œμ—μ„œ ν•΄λ°©λ˜λŠ” 건 μ•„λ‹ˆλ‹€. (λ‚΄λΆ€ κ΅¬ν˜„μ— 가변인 낑은 κ°’ νƒ€μž…μ„ μ‚¬μš©ν•˜λ˜ μž”μž¬κ°€ λ‚¨μ•„μžˆλ‹€.)
  • μ™ΈλΆ€ κ³΅κ²©μœΌλ‘œλΆ€ν„° Period μΈμŠ€ν„΄μŠ€μ˜ λ‚΄λΆ€λ₯Ό λ³΄ν˜Έν•˜λ €λ©΄ μƒμ„±μžμ—μ„œ 받은 κ°€λ³€ λ§€κ°œλ³€μˆ˜ 각각을 λ°©μ–΄μ μœΌλ‘œ 볡사(defensive copy) ν•΄μ•Ό ν•œλ‹€.
  • Period μΈμŠ€ν„΄μŠ€ μ•ˆμ—μ„œλŠ” 원본이 μ•„λ‹Œ 볡사본을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.
// μƒˆλ‘œ μž‘μ„±ν•œ μƒμ„±μžλ₯Ό μ‚¬μš©ν•˜λ©΄ μ•žμ„œμ˜ 곡격은 더 이상 Period에 μœ„ν˜‘μ΄ λ˜μ§€ μ•ŠλŠ”λ‹€. 
// λ§€κ°œλ³€μˆ˜μ˜ μœ νš¨μ„± κ²€μ‚¬ν•˜κΈ° 전에 방어적 볡사본을 λ§Œλ“€κ³  이 λ³΅μ‚¬λ³ΈμœΌλ‘œ μœ νš¨μ„± 검사λ₯Ό ν•œλ‹€. 
μ½”λ“œ 50-3 μˆ˜μ •ν•œ μƒμ„±μž - λ§€κ°œλ³€μˆ˜μ˜ 방어적 볡사본을 λ§Œλ“ λ‹€. (304μͺ½)
    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 + "κ°€ " + this.end + "보닀 λŠ¦λ‹€.");
    }

  • 방어적 볡사에 Date의 clone λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ€ 점에도 μ£Όλͺ©ν•˜μž.
  • DateλŠ” final이 μ•„λ‹ˆλ―€λ‘œ clone이 Dateκ°€ μ •μ˜ν•œ 게 아닐 수 μžˆλ‹€.

λ§€κ°œλ³€μˆ˜κ°€ 제3μžμ— μ˜ν•΄ ν™•μž₯될 수 μžˆλŠ” νƒ€μž…μ΄λΌλ©΄ 방어적 볡사본을 λ§Œλ“€ λ•Œ clone을 μ‚¬μš©ν•΄μ„œλŠ” μ•ˆ λœλ‹€.

μƒμ„±μžλ₯Ό μˆ˜μ •ν•˜λ©΄ μ•žμ„œμ˜ 곡격은 막아낼 수 μžˆμ§€λ§Œ Period μΈμŠ€ν„΄μŠ€λŠ” 아직도 λ³€κ²½ κ°€λŠ₯ν•˜λ‹€. (μ ‘κ·Όμž λ©”μ„œλ“œκ°€ λ‚΄λΆ€μ˜ κ°€λ³€ 정보λ₯Ό 직접 λ“œλŸ¬λ‚΄κΈ° λ•Œλ¬Έμ΄λ‹€.

μƒμ„±μžλ₯Ό μˆ˜μ •ν•˜λ©΄ μ•žμ„œμ˜ 곡격은 막아낼 수 μžˆμ§€λ§Œ Period μΈμŠ€ν„΄μŠ€λŠ” 아직도 λ³€κ²½ κ°€λŠ₯ν•˜λ‹€. μ ‘κ·Όμž λ©”μ„œλ“œκ°€ λ‚΄λΆ€μ˜ κ°€λ³€ 정보λ₯Ό 직접 λ“œλŸ¬λ‚΄κΈ° λ•Œλ¬Έμ΄λ‹€.

λ‘λ²ˆμ§Έ 곡격을 막아내렀면 λ‹¨μˆœνžˆ μ ‘κ·Όμžκ°€ κ°€λ³€ ν•„λ“œμ˜ 방어적 볡사본을 λ°˜ν™˜ν•˜λ©΄ λœλ‹€.

// 'λΆˆλ³€'인 Period의 λ‚΄λΆ€λ₯Ό κ³΅κ²©ν•˜λŠ” 두 가지 예 (303-305μͺ½)
public class Attacks {
    public static void main(String[] args) {
        // μ½”λ“œ 50-4 Period μΈμŠ€ν„΄μŠ€λ₯Ό ν–₯ν•œ 두 번째 곡격 (305μͺ½)
        start = new Date();
        end = new Date();
        p = new Period(start, end);
        p.end().setYear(78);  // p의 λ‚΄λΆ€λ₯Ό λ³€κ²½ν–ˆλ‹€!
        System.out.println(p);
    }
}
// μˆ˜μ •ν•œ μ ‘κ·Όμž - ν•„λ“œμ˜ 방어적 볡사본을 λ°˜ν™˜ν•œλ‹€. 
// μƒˆλ‘œμš΄ μ ‘κ·ΌμžκΉŒμ§€ κ°–μΆ”λ©΄ PeriodλŠ” μ™„λ²½ν•œ λΆˆλ³€μœΌλ‘œ κ±°λ“­λ‚œλ‹€. 
// μ‹œμž‘ μ‹œκ°μ΄ μ’…λ£Œ μ‹œκ°λ³΄λ‹€ λ‚˜μ€‘μΌ 수 μ—†λ‹€λŠ” λΆˆλ³€μ‹μ„ μœ„λ°°ν•  방법은 μ—†λ‹€. 
// Period μžμ‹  λ§κ³ λŠ” κ°€λ³€ ν•„λ“œμ— μ ‘κ·Όν•  방법이 μ—†μœΌλ‹ˆ ν™•μ‹€ν•˜λ‹€. 
> λͺ¨λ“  ν•„λ“œκ°€ 객체 μ•ˆμ— μ™„λ²½ν•˜κ²Œ μΊ‘μŠν™”λ˜μ—ˆλ‹€. 
    public Date start() {
        return new Date(start.getTime());
    ]
    public Date end() {
        return new Date(end.getTime());
    }

  • μƒμ„±μžμ™€ 달리 μ ‘κ·Όμž λ©”μ„œλ“œλŠ” 방어적 볡사에 clone을 μ‚¬μš©ν•΄λ„ λœλ‹€.
  • Periodκ°€ 가지고 μžˆλŠ” Date κ°μ²΄λŠ” java.util.Dateμž„μ΄ ν™•μ‹€ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€. (μ‹ λ’°ν•  수 μ—†λŠ” ν•˜μœ„ ν΄λž˜μŠ€κ°€ μ•„λ‹ˆλ‹€) 그렇더라도 μ•„μ΄ν…œ 13(clone μž¬μ •μ˜λŠ” μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•˜λΌ)μ—μ„œ μ„€λͺ…ν•œ 이유 λ•Œλ¬Έμ— μΈμŠ€ν„΄μŠ€λ₯Ό λ³΅μ‚¬ν•˜λŠ” λ°λŠ” 일반적으둜 μƒμ„±μžλ‚˜ 정적 νŒ©ν„°λ¦¬λ₯Ό μ“°λŠ”κ²Œ μ’‹λ‹€.
  • 방어적 λ³΅μ‚¬μ—λŠ” μ„±λŠ₯ μ €ν•˜κ°€ λ”°λ₯΄κ³  또 항상 μ“Έ 수 μžˆλŠ” 것도 μ•„λ‹ˆλ‹€.
  • (같은 νŒ¨ν‚€μ§€μ— μ†ν•˜λŠ” λ“±μ˜ 이유둜) ν˜ΈμΆœμžκ°€ μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€λ₯Ό μˆ˜μ •ν•˜μ§€ μ•ŠμœΌλ¦¬λΌ ν™•μ‹ ν•˜λ©΄ 방어적 볡사λ₯Ό μƒλž΅ν•  수 μžˆλ‹€.
  • μ΄λŸ¬ν•œ 상황이라도 ν˜ΈμΆœμžμ—μ„œ ν•΄λ‹Ή λ§€κ°œλ³€μˆ˜λ‚˜ λ°˜ν™˜κ°’μ„ μˆ˜μ •ν•˜μ§€ 말아야 함을 λͺ…ν™•νžˆ λ¬Έμ„œν™”ν•˜λŠ” 게 μ’‹λ‹€.

핡심 정리

ν΄λž˜μŠ€κ°€ ν΄λΌμ΄μ–ΈνŠΈλ‘œλΆ€ν„° λ°›λŠ” ν˜Ήμ€ ν΄λΌμ΄μ–ΈνŠΈλ‘œ λ°˜ν™˜ν•˜λŠ” κ΅¬μ„±μš”μ†Œκ°€ 가변이라면 κ·Έ μš”μ†ŒλŠ” λ°˜λ“œμ‹œ λ°©μ–΄μ μœΌλ‘œ 볡사해야 ν•œλ‹€.