[Effective Java 3/e] 아이템 18 - 상속보다는 컴포지션을 사용하라


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

래퍼 클래스 - 상속 대신 컴포지션을 사용했다

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 add(E e) { return s.add(e); }
  public boolean addAll(Collection<? extends E> c) { return super.addAll(c); }
  ...
}
  • InstrumentedSet은 HashSet의 모든 기능을 정의한 Set 인터페이스를 활용해 설계되어 견고하고 유연하다.
    • 임의의 Set에 계측 기능을 덧씌워 새로운 Set으로 만드는 것이 이 클래스의 핵심
    • 상속 방식은 구체 클래스 각각을 따로 확장해야 함
    • 지원하고 싶은 상위 클래스의 생성자 각각에 대응하는 생성자를 별도로 정의해줘야 함
    • 한 번만 구현해두면 어떠한 Set 구현체라도 계측 가능하면, 기존 생성자들과도 함께 사용 가능
      • Set times = new InstrumentedSet<>(new TreeSet<>(cmp));
      • Set s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));

정리

  • 상속은 강력하지만 캡슐화를 해치는 문제가 있다.
  • 상속은 클래스와 하위 클래스가 순수한 is-a 관계일 때만 써야 한다.
    • is-a 관계이여도 하위 클래스의 패키지가 상위 클래스와 다르고, 상위 클래스가 확장을 고려해 설계되지 않았다면 문제가 될 수 있다.
  • 상속 취약점을 피할려면 상속 대신 컴포지션과 전달을 사용하자.
    • 래퍼 클래스로 구현할 인터페이스가 있다면 더욱 권장
    • 래퍼 클래스는 하위 클래스보다 견고하고 강력