Growth-Collectors / effective-java

repository for effective java study
3 stars 2 forks source link

아이템 18. 상속보다는 컴포지션을 사용하라 #18

Open eternalklaus opened 1 year ago

jioome commented 1 year ago

[Item18] 상속보다는 컴포지션을 사용하라

Intro

잘못된 예 - 상속을 잘못 사용했다!

HashMap 기능을 사용하면서, 생성된 이후 몇개의 원소가 더해졌는지 알 수 있는 기능을 추가한 클래스를 구현


public class InstrumentedHashSet<E> extends HashSet<E> {
  private int addCount = 0;

  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("용", "용용", "용용용"));

getAddCount 를 호출하면 3이 반환될 것 같지만 실제로는 6을 반환한다.

public abstract class AbstractCollection<E> implements Collection<E> {
    // ...

    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

    // ...
}

이 예제의 경우, addAll 메서드를 재정의하지 않거나, 다른 식의 재정의를 통해 문제를 해결할 수 있다.

다음 릴리스에서 상위 클래스에 새로운 메서드를 추가한다면?

문제들을 해결할 좋은 방법이 있다!

컴포지션이란?

기존 클래스를 확장하지 않고, 새로운 클래스를 만든 후, private 필드로 기존 클래스의 인스턴스를 참조하게 하도록 설계하는 방법이다.

→ 기존 클래스가 새로운 클래스의 구성요소로 쓰이게 되는 구조

HashMap 예제를 컴포지션으로 변환

public class InstrumentedHashSetUseComposition<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedHashSetUseComposition(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;
    }
}

아래는 재사용할 수 있는 전달클래스인 ForwardingSet

public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;

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

    public int size() {
        return 0;
    }

    public boolean isEmpty() {
        return s.isEmpty();
    }

    public boolean contains(Object o) {
        return s.contains(o);
    }

    public Iterator<E> iterator() {
        return s.iterator();
    }

    public Object[] toArray() {
        return s.toArray();
    }

    public <T> T[] toArray(T[] a) {
        return s.toArray(a);
    }

    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 retainAll(Collection<?> c) {
        return s.retainAll(c);
    }

    public boolean removeAll(Collection<?> c) {
        return s.removeAll(c);
    }

    public void clear() {
        s.clear();
    }

    @Override
    public boolean equals(Object o) {
        return s.equals(o);
    }

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

    @Override
    public String toString() {
        return s.toString();
    }
}

용어

컴포지션의 장점

컴포지션의 단점

래퍼 클래스와 SELF 문제

상속과 is-a

핵심 정리

YunDaHyee commented 1 year ago

컴포지션과 전달 개념이 좀 헷갈려서 찾고 정리해봤습니다.

상속 개념이 단순하지만 단순하지 않은 개념이었네요