item 18 dodo4513 - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

μ•„μ΄ν…œ 18 μƒμ†λ³΄λ‹€λŠ” μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•˜λΌ

  • 상속은 μ½”λ“œλ₯Ό μž¬μ‚¬μš©ν•˜λŠ” κ°•λ ₯ν•œ μˆ˜λ‹¨μ΄μ§€λ§Œ, 항상 μ΅œμ„ μ€ μ•„λ‹ˆλ‹€.
  • μƒμœ„ ν΄λž˜μŠ€μ™€ ν•˜μœ„ 클래슀λ₯Ό λͺ¨λ‘ 같은 ν”„λ‘œκ·Έλž˜λ¨Έκ°€ ν†΅μ œν•˜λŠ” νŒ¨ν‚€μ§€ μ•ˆμ—μ„œλΌλ©΄ 상속도 μ•ˆμ „ν•œ 방법이닀. ν™•μž₯ν•  λͺ©μ μœΌλ‘œ μ„€κ³„λ˜μ—ˆκ³  λ¬Έμ„œν™”λ„ 잘 된 클래슀(μ•„μ΄ν…œ 19)도 λ§ˆμ°¬κ°€μ§€λ‘œ μ•ˆμ „ν•˜λ‹€. ν•˜μ§€λ§Œ 일반적인 ꡬ체 클래슀λ₯Ό νŒ¨ν‚€μ§€ 경계λ₯Ό λ„˜μ–΄, 즉 λ‹€λ₯Έ νŒ¨ν‚€μ§€μ˜ ꡬ체 클래슀λ₯Ό μƒμ†ν•˜λŠ” 일은 μœ„ν—˜ν•˜λ‹€.
  • λ©”μ„œλ“œ 호좜과 달리 상속은 μΊ‘μŠν™”λ₯Ό κΉ¨λœ¨λ¦°λ‹€. μƒμœ„ ν΄λž˜μŠ€λŠ” λ¦΄λ¦¬μŠ€λ§ˆλ‹€ λ‚΄λΆ€ κ΅¬ν˜„μ΄ λ‹¬λΌμ§ˆ 수 있으며, κ·Έ μ—¬νŒŒλ‘œ μ½”λ“œ ν•œ 쀄 κ±΄λ“œλ¦¬μ§€ μ•Šμ€ ν•˜μœ„ ν΄λž˜μŠ€κ°€ μ˜€λ™μž‘ν•  수 μžˆλ‹€λŠ” 말이닀

상속을 잘λͺ» μ‚¬μš©ν•œ μΌ€μ΄μŠ€

public class InstrumentedHashSet<E> extends HashSet<E> {

    // μΆ”κ°€λœ μ›μ†Œμ˜ 수
    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}

μœ„ μ½”λ“œ λ² μ΄μŠ€μ—μ„œ μ•„λž˜ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λŠ” 경우 잘λͺ»λœ κ²°κ³Όκ°€ 좜λ ₯λœλ‹€.

InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("ν‹±", "탁탁", "νŽ‘"));
  • s.의 sizeλŠ” 3을 μ˜ˆμƒν•˜μ§€λ§Œ μ‹€ν–‰μ‹œμΌœλ³΄λ©΄ 6이 λ‚˜μ˜¨λ‹€.
  • InstrumentedHashSet addAll()의 λ‚΄λΆ€μ—μ„œ addCountλ₯Ό λ”ν•˜κ³  super.addAll(c) 둜 HashSet의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν–ˆλ‹€. HashSet의 addAll()은 add()λ₯Ό 맀번 ν˜ΈμΆœν•˜λŠ” λ°©μ‹μœΌλ‘œ κ΅¬ν˜„λ˜μ–΄ μžˆλŠ”λ°, InstrumentedHashSet의 add()에 addCount++ 연산이 있기 λ•Œλ¬Έμ— 크기가 2배둜 λ‚˜μ˜¨ 것이닀.

  • 이 처럼 μžμ‹ μ˜ λ‹€λ₯Έ 뢀뢄을 μ‚¬μš©ν•˜λŠ” 뢀뢄은 ν•΄λ‹Ή 클래슀의 λ‚΄λΆ€ κ΅¬ν˜„ 방식에 μ˜μ‘΄ν•  수 밖에 μ—†λ‹€. InstrumentedHashSetλŠ” κΉ¨μ§€μ‹œ μ‰¬μš΄ 클래슀인 것이닀.
  • λ˜ν•œ HashSet에 μƒˆλ‘œμš΄ λ©”μ„œλ“œκ°€ 좔가됐을 λ•Œ, InstrumentedHashSetκ°€ 이λ₯Ό 적절히 override ν•˜μ§€ λͺ»ν–ˆλ‹€λ©΄ λ³΄μ•ˆ 취약점이 될 μˆ˜λ„ μžˆλ‹€.

해결책은 ꡬ성(Composition)이닀!

  • κΈ°μ‘΄ 클래슀λ₯Ό ν™•μž₯ν•˜λŠ” λŒ€μ‹ , μƒˆλ‘œμš΄ 클래슀λ₯Ό λ§Œλ“€κ³  private ν•„λ“œλ‘œ κΈ°μ‘΄ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν•˜κ²Œ ν•˜μž.
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;
    public InstrumentedSet(Set<E> s) {
        super(s);
    }
    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }
}

public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }
    public void clear() { s.clear(); }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty() { return s.isEmpty(); }
    public int size() { return s.size(); }
    public Iterator<E> iterator() { return s.iterator(); }
    public boolean add(E e) { return s.add(e); }
    public boolean remove(Object o) { return s.remove(o); }
    public boolean containsAll(Collection<?> c)
    { return s.containsAll(c); }
    public boolean addAll(Collection<? extends E> c)
    { return s.addAll(c); }
    public boolean removeAll(Collection<?> c)
    { return s.removeAll(c); }
    public boolean retainAll(Collection<?> c)
    { return s.retainAll(c); }
    public Object[] toArray() { return s.toArray(); }
    public <T> T[] toArray(T[] a) { return s.toArray(a); }
    @Override public boolean equals(Object o)
    { return s.equals(o); }
    @Override public int hashCode() { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }
}
  • ꡬ체적으둜 Set μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν–ˆκ³  Set μΈμŠ€ν„΄μŠ€λ₯Ό 인수둜 λ°›λŠ” μƒμ„±μžλ₯Ό ν•˜λ‚˜ μ œκ³΅ν•œλ‹€. μž„μ˜μ˜ Set에 계츑 κΈ°λŠ₯을 λ§μ”Œμ›Œ μƒˆλ‘œμš΄ Set으둜 λ§Œλ“œλŠ” 것이 이 클래슀의 핡심이닀.
  • ν•œ 번만 κ΅¬ν˜„ν•΄λ‘λ©΄ μ–΄λ– ν•œ Set κ΅¬ν˜„μ²΄λΌλ„ ν•¨κ»˜ μ‚¬μš©ν•  수 μžˆλ‹€.

  • λ‹€λ₯Έ Set μΈμŠ€ν„΄μŠ€λ₯Ό 감싸고(wrap) μžˆλ‹€λŠ” λœ»μ—μ„œ InstrumentedSet 같은 클래슀λ₯Ό 래퍼 클래슀라 ν•˜λ©°, λ‹€λ₯Έ Set에 계츑 κΈ°λŠ₯을 λ§μ”Œμš΄λ‹€λŠ” λœ»μ—μ„œ λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄(Decorator pattern)이라고 ν•œλ‹€. μ»΄ν¬μ§€μ…˜κ³Ό μ „λ‹¬μ˜ 쑰합은 넓은 의미둜 μœ„μž„(delegation)이라고 λΆ€λ₯Έλ‹€. 단, μ—„λ°€νžˆ λ”°μ§€λ©΄ 래퍼 객체가 λ‚΄λΆ€ 객체에 자기 μžμ‹ μ˜ μ°Έμ‘°λ₯Ό λ„˜κΈ°λŠ” 경우만 μœ„μž„μ— ν•΄λ‹Ήν•œλ‹€.

  • 래퍼 ν΄λž˜μŠ€κ°€ 콜백 ν”„λ ˆμž„μ›Œν¬μ™€λŠ” μ–΄μšΈλ¦¬μ§€ μ•ŠλŠ”λ‹€λŠ” 점을 μ£Όμ˜ν•˜μž. λ‚΄λΆ€ κ°μ²΄λŠ” μ™ΈλΆ€μ˜ 쑴재λ₯Ό λͺ¨λ₯΄κ³  μžμ‹ μ˜ μ°Έμ‘°λ₯Ό λ„˜κΈ°κ²Œ 되면 μ½œλ°±λ•ŒλŠ” λž˜νΌκ°€ μ•„λ‹Œ λ‚΄λΆ€ 객체λ₯Ό ν˜ΈμΆœν•˜κ²Œ λœλ‹€. 이λ₯Ό self 문제라고 ν•œλ‹€.

음 그럼 상속은 μ–Έμ œ μ‚¬μš©ν•˜μ§€?

  • 상속은 λ°˜λ“œμ‹œ ν•˜μœ„ ν΄λž˜μŠ€κ°€ μƒμœ„ 클래슀의 β€˜μ§„μ§œβ€™ ν•˜μœ„ νƒ€μž…μΈ μƒν™©μ—μ„œλ§Œ μ“°μ—¬μ•Ό ν•œλ‹€. λ‹€λ₯΄κ²Œ λ§ν•˜λ©΄, 클래슀 Bκ°€ 클래슀 A와 is-a 관계일 λ•Œλ§Œ 클래슀 Aλ₯Ό 상속해야 ν•œλ‹€.

  • μ»΄ν¬μ§€μ…˜μ„ 써야 ν•  μƒν™©μ—μ„œ 상속을 μ‚¬μš©ν•˜λŠ” 건 λ‚΄λΆ€ κ΅¬ν˜„μ„ λΆˆν•„μš”ν•˜κ²Œ λ…ΈμΆœν•˜λŠ” 꼴이닀. κ·Έ κ²°κ³Ό APIκ°€ λ‚΄λΆ€ κ΅¬ν˜„μ— 묢이고 κ·Έ 클래슀의 μ„±λŠ₯도 μ˜μ›νžˆ μ œν•œλœλ‹€. 더 μ‹¬κ°ν•œ λ¬Έμ œλŠ” ν΄λΌμ΄μ–ΈνŠΈκ°€ λ…ΈμΆœλœ 내뢀에 직접 μ ‘κ·Όν•  수 μžˆλ‹€λŠ” 점이닀.

  • μ»΄ν¬μ§€μ…˜μœΌλ‘œλŠ” 이런 결함을 μˆ¨κΈ°λŠ” μƒˆλ‘œμš΄ APIλ₯Ό 섀계할 수 μžˆμ§€λ§Œ, 상속은 μƒμœ„ 클래슀의 APIλ₯Ό β€˜κ·Έ κ²°ν•¨κΉŒμ§€λ„β€™ κ·ΈλŒ€λ‘œ μŠΉκ³„ν•œλ‹€.

핡심정리

상속은 κ°•λ ₯ν•˜μ§€λ§Œ μΊ‘μŠν™”λ₯Ό ν•΄μΉœλ‹€λŠ” λ¬Έμ œκ°€ μžˆλ‹€.
상속은 μƒμœ„ ν΄λž˜μŠ€μ™€ ν•˜μœ„ ν΄λž˜μŠ€κ°€ μˆœμˆ˜ν•œ is-a 관계일 λ•Œλ§Œ 써야 ν•œλ‹€. is-a 관계일 λ•Œλ„ μ•ˆμ‹¬ν•  μˆ˜λ§Œμ€ μ—†λŠ” 게, ν•˜μœ„ 클래슀의 νŒ¨ν‚€μ§€κ°€ μƒμœ„ ν΄λž˜μŠ€μ™€ λ‹€λ₯΄κ³ , μƒμœ„ ν΄λž˜μŠ€κ°€ ν™•μž₯을 κ³ λ €ν•΄ μ„€κ³„λ˜μ§€ μ•Šμ•˜λ‹€ λ©΄ μ—¬μ „νžˆ λ¬Έμ œκ°€ 될 수 μžˆλ‹€.
μƒμ†μ˜ 취약점을 ν”Όν•˜λ €λ©΄ 상속 λŒ€μ‹  μ»΄ν¬μ§€μ…˜κ³Ό 전달을 μ‚¬μš©ν•˜μž.
특히 래퍼 클래슀둜 κ΅¬ν˜„ν•  μ λ‹Ήν•œ μΈν„°νŽ˜μ΄μŠ€κ°€ μžˆλ‹€λ©΄ λ”μš± κ·Έλ ‡λ‹€.
래퍼 ν΄λž˜μŠ€λŠ” ν•˜μœ„ ν΄λž˜μŠ€λ³΄λ‹€ κ²¬κ³ ν•˜κ³  κ°•λ ₯ν•˜λ‹€.

⚠️ **GitHub.com Fallback** ⚠️