Item 18. Composition

상속보닀 μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•˜λΌ

** 이번 μ•„μ΄ν…œμ—μ„œ λ…Όν•˜λŠ” λ¬Έμ œλŠ” ν΄λž˜μŠ€κ°€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ±°λ‚˜ μΈν„°νŽ˜μ΄μŠ€κ°€ λ‹€λ₯Έ μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν™•μž₯ν•  λ•Œμ™€λŠ” λ¬΄κ΄€ν•œ λ¬Έμ œλ“€μ„ λ‹€λ£Έ

상속은 μ½”λ“œλ₯Ό μž¬μ‚¬μš©ν•˜κΈ° μœ„ν•΄ μœ μš©ν•œ λ„κ΅¬μ΄μ§€λ§Œ, 항상 μ΅œμ„ μ΄ μ•„λ‹ˆλ©°, 잘λͺ» μ‚¬μš©ν•˜κ²Œλ˜λ©΄ 였λ₯˜λ₯Ό λ‚΄κΈ° μ‰½κ²Œ λœλ‹€. μ•„λž˜μ˜ κ²½μš°μ—λŠ” 상속을 μ‚¬μš©ν•˜λ”λΌλ„ μ•ˆμ „ν•˜λ‹€.

  • μƒμœ„ ν΄λž˜μŠ€μ™€ ν•˜μœ„ 클래슀 λͺ¨λ‘ λ™μΌν•œ ν”„λ‘œκ·Έλž˜λ¨Έκ°€ ν†΅μ œν•˜λŠ” νŒ¨ν‚€μ§€ μ•ˆμ—μ„œλ§Œ μ‚¬μš©

  • ν™•μž₯ν•  λͺ©μ μœΌλ‘œ μ„€κ³„λ˜μ—ˆκ³  λ¬Έμ„œν™”λ„ 잘 λ˜μ–΄μžˆλŠ” 클래슀λ₯Ό μƒμ†ν•˜λŠ” 경우

ν•˜μ§€λ§Œ λ°˜λŒ€μ˜ 상황(νŒ¨ν‚€μ§€ 경계λ₯Ό λ„˜κ±°λ‚˜, λ¬Έμ„œν™”κ°€ λΆ€μ‘±ν•˜κ±°λ‚˜, ν™•μž₯을 κ³ λ €ν•˜μ§€ μ•Šμ€ 클래슀λ₯Ό μƒμ†ν•˜λŠ” 경우)μ—μ„œλŠ” 상속은 μœ„ν—˜ν•˜λ‹€.

μƒμ†μ˜ μœ„ν—˜μ„±

상속을 ν•˜κ²Œ 되면 μƒμœ„ 클래슀의 κ΅¬ν˜„μ— 따라 ν•˜μœ„ 클래슀의 λ™μž‘μ— 영ν–₯을 미치기 λ•Œλ¬Έμ— κ²°κ΅­ μΊ‘μŠν™”λ₯Ό μœ„λ°˜ν•˜κ²Œ λœλ‹€. μƒμœ„ 클래슀 섀계 μ‹œ ν™•μž₯을 μΆ©λΆ„νžˆ κ³ λ €ν•˜μ§€ μ•Šκ³  λ¬Έμ„œν™”λ₯Ό 잘 해두지 μ•ŠμœΌλ©΄ ν•˜μœ„ ν΄λž˜μŠ€λŠ” μƒμœ„ 클래슀의 변경에 μ·¨μ•½ν•΄μ§€κ²Œ λœλ‹€.

λ©”μ„œλ“œ μ˜€λ²„λΌμ΄λ”©

μ•„λž˜λŠ” HashSet을 μƒμ†ν•˜μ—¬ add / addAll을 μ˜€λ²„λΌμ΄λ”©ν•˜μ˜€μ§€λ§Œ μ œλŒ€λ‘œ λ™μž‘ν•˜μ§€ μ•ŠλŠ” μ˜ˆμ‹œμ΄λ‹€.

class InstrumentedHashSet<E> extends HashSet<E> {

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

    public InstrumentedHashSet() {
    }

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

    @Override
    public boolean add(E e) {
        addCount++; // 4. 각 μ›μ†Œκ°€ 좔가될 λ•Œλ§ˆλ‹€ addCount 증가
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size(); // 2. addCount 증가
        return super.addAll(c); // 3. μƒμœ„ 클래슀의 addAll 호좜, addAll은 λ‚΄λΆ€μ μœΌλ‘œ addλ₯Ό 호좜
    }

    public int getAddCount() {
        return addCount;
    }
}

class Main {

    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
        s.addAll(List.of("ν‹±", "탁탁", "νŽ‘")); // 1. addAll 호좜
        System.out.println(s.getAddCount()); // 5. κΈ°λŒ€ν•˜λŠ” 3이 μ•„λ‹Œ 6이 좜λ ₯됨
    }
}

μœ„μ˜ μ£Όμ„μ—μ„œ λ³Ό 수 μžˆλ“―μ΄ addAll λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄μ„œ addCountκ°€ 3이 증가할 κ²ƒμœΌλ‘œ κΈ°λŒ€ν•˜μ§€λ§Œ, μ‹€μ œλ‘œλŠ” 6이 μ¦κ°€ν•˜κ²Œ λœλ‹€. HashSet의 addAll λ©”μ„œλ“œλŠ” λ‚΄λΆ€μ μœΌλ‘œ add λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ³  μžˆμ§€λ§Œ, ν”„λ‘œκ·Έλž˜λ¨Έκ°€ 이λ₯Ό μΈμ§€ν•˜κΈ° 쉽지 μ•ŠκΈ° λ•Œλ¬Έμ— μœ„μ™€ 같은 λ¬Έμ œκ°€ λ°œμƒν•˜κ²Œ λœλ‹€. μΌμ‹œμ μœΌλ‘œ addAll λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜μ—¬ 직접 μ»¬λ ‰μ…˜μ„ μˆœνšŒν•˜λ©° add λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ„λ‘ μˆ˜μ •ν•˜λ©΄ λ¬Έμ œλŠ” ν•΄κ²°λ˜μ§€λ§Œ, λ‹€λ₯Έ 였λ₯˜λ₯Ό λ‚΄κΈ° μ‰¬μš΄ μ½”λ“œκ°€ λ˜μ–΄λ²„λ¦°λ‹€.

μƒˆλ‘œμš΄ λ©”μ„œλ“œ μΆ”κ°€

μœ„μ˜ λ¬Έμ œλŠ” μ˜€λ²„λΌμ΄λ”©ν•˜λ©΄μ„œ λ°œμƒν•˜λŠ” λ¬Έμ œμ΄μ§€λ§Œ, μƒμœ„ ν΄λž˜μŠ€μ— μƒˆλ‘œμš΄ λ©”μ„œλ“œκ°€ μΆ”κ°€λ˜λŠ” κ²½μš°μ—λ„ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€. λ§Œμ•½ μƒˆλ‘œ μΆ”κ°€ν•œ λ©”μ„œλ“œκ°€ μƒμœ„ 클래슀의 λ‹€μŒ λ²„μ „μ—μ„œ μƒˆλ‘­κ²Œ μΆ”κ°€λœ λ©”μ„œλ“œμ™€ λ™μΌν•œ 이름을 가진닀면, ν™•μž₯ν•œ ν•˜μœ„ ν΄λž˜μŠ€λŠ” 컴파일쑰차 λ˜μ§€ μ•Šκ±°λ‚˜ μ˜€λ²„λΌμ΄λ”©μ„ ν•˜κ²Œ λ˜μ–΄ 잘λͺ»λœ λ™μž‘μ„ ν•˜κ²Œ λœλ‹€.

문제λ₯Ό νšŒν”Όν•˜λŠ” 방법

상속을 μ‚¬μš©ν•˜μ§€ μ•Šκ³ λ„ μƒμœ„ 클래슀의 κΈ°λŠ₯을 μž¬μ‚¬μš©ν•  수 μžˆλŠ” 방법이 μžˆλŠ”λ°, λ°”λ‘œ μ»΄ν¬μ§€μ…˜μ΄λ‹€. μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•˜λŠ” 방법은 μ•„λž˜μ™€ κ°™λ‹€.

  1. ν™•μž₯ν•˜λŠ” λŒ€μ‹  μƒˆλ‘œμš΄ 클래슀 생성

  2. private ν•„λ“œλ‘œ κΈ°μ‘΄ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν•˜λŠ” ν•„λ“œ μΆ”κ°€(= composition)

  3. μƒˆλ‘œμš΄ 클래슀의 μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œμ—μ„œ κΈ°μ‘΄ 클래슀의 λŒ€μ‘ν•˜λŠ” λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ κ²°κ³Όλ₯Ό λ°˜ν™˜(= forwarding)

    • 래퍼 클래슀(wrapper class): κΈ°μ‘΄ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό ν•„λ“œλ‘œ κ°–κ³  μžˆλŠ” μƒˆ 클래슀

    • 전달 λ©”μ„œλ“œ(forwarding method): κΈ°μ‘΄ 클래슀λ₯Ό ν˜ΈμΆœν•΄μ£ΌλŠ” μƒˆ 클래슀의 λ©”μ„œλ“œ

μœ„ κ΅¬ν˜„ 방법을 λ°”νƒ•μœΌλ‘œ 전달 클래슀λ₯Ό λ§Œλ“€μ–΄ μƒˆλ‘œμš΄ InstrumentedHashSet 클래슀λ₯Ό κ΅¬ν˜„ν•˜λ©΄ μ•„λž˜μ™€ κ°™λ‹€.

// Wrapper Class
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;
    }
}

// Forwarding Class
class ForwardingSet<E> implements Set<E> {

    private final Set<E> s;

    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

    @Override
    public int size() {
        return s.size();
    }

    // μœ„μ™€ 같이 κ·ΈλŒ€λ‘œ μ „λ‹¬ν•˜λŠ” λ‚˜λ¨Έμ§€ λ©”μ„œλ“œλ“€
}

μœ„ μ½”λ“œλŠ” Set μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν™œμš©ν•΄μ„œ 전달 클래슀λ₯Ό λ§Œλ“€μ–΄ λ‹€λ₯Έ Set κ΅¬ν˜„μ²΄μ—λ„ μ μš©ν•  수 μžˆλ„λ‘ μ„€κ³„ν•˜μ—¬ 맀우 μœ μ—°ν•˜κ²Œ μ„€κ³„λœ μ˜ˆμ‹œμ΄λ‹€. 상속 방식은 ꡬ체 클래슀λ₯Ό 각각 상속받아 κ΅¬ν˜„ν•΄μ•Ό ν•˜μ§€λ§Œ, μ»΄ν¬μ§€μ…˜ 방식은 μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν™œμš©ν•˜μ—¬ κ΅¬ν˜„μ²΄μ— 상관없이 μž¬μ‚¬μš©ν•  수 μžˆλ‹€.

상속을 μ‚¬μš©ν•΄μ•Όν•˜λŠ” 상황

μ•„λž˜ 두 쑰건을 λ§Œμ‘±ν•˜λŠ” κ²½μš°μ—λŠ” 상속을 μ‚¬μš©ν•΄λ„ μ•ˆμ „ν•˜μ§€λ§Œ, κ·Έ μ™Έμ˜ κ²½μš°μ—λŠ” 상속을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€.

  • 두 클래슀의 관계가 is-a 관계인 경우 (ν•˜μœ„ 클래슀 is a μƒμœ„ 클래슀)

    • BλŠ” Aλ‹€ λΌλŠ” λ¬Έμž₯이 μ„±λ¦½ν•΄μ•Όλ§Œ ν•œλ‹€.

  • μƒμ†ν•˜λ €λŠ” μƒμœ„ ν΄λž˜μŠ€κ°€ μ²˜μŒμ— λͺ…μ‹œ 된 쑰건(동일 νŒ¨ν‚€μ§€, λ¬Έμ„œν™”, ν™•μž₯을 κ³ λ €ν•œ 섀계)을 λ§Œμ‘±ν•˜λŠ” 경우

μžλ°” 라이브러리의 Stack/Vector, Properties/Hashtable 등은 상속을 μ‚¬μš©ν•˜μ˜€μ§€λ§Œ, is-a 관계가 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— 잘λͺ»λœ 섀계라고 λ³Ό 수 μžˆλ‹€.

Last updated