이번 아이템에서 논하는 문제는 인터페이스 상속과는 무관하다.
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;
}
}
// 아래의 코드를 실행하고 getAddCount 메서드를 호출하면 6이 반환됩니다.
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("틱", "탁탁", "펑"));
위와 같은 문제가 생긴 이유는 ?
하위 클래스에서 addAll 메서드를 재정의하지 않으면 되는거 아닌가?
addAll 메서드를 다른 식으로 재정의할 수 있는거 아닌가?
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s;}
@Override
public void clear() { s.clear();}
@Override
public boolean contains(Object o) { return s.contains(o);}
@Override
public boolean isEmpty() { return s.isEmpty();}
@Override
public int size() { return s.size();}
@Override
public Iterator<E> iterator() { return s.iterator();}
@Override
public boolean add(E e) { return s.add(e);}
@Override
public boolean remove(Object o) { return s.remove(o);}
@Override
public boolean containsAll(Collection<?> c) { return s.containsAll(c);}
@Override
public boolean addAll(Collection<? extends E> c) { return s.addAll(c);}
@Override
public boolean removeAll(Collection<?> c) { return s.removeAll(c);}
@Override
public boolean retainAll(Collection<?> c) { return s.retainAll(c);}
@Override
public Object[] toArray() { return s.toArray();}
@Override
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<Instant> times = new InstrumentedSet<>(new TreeSet<>(cmp));
Set<E> s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));
static void walk(Set<Dog> dogs) {
InstrumentedSet<Dog> iDogs = new InstrumentedSet<>(dogs);
..// 해당 메서드에서는 dogs 대신 iDogs를 사용한다.
}
상속은 캡슐화를 해치는 문제가 있어서 순수한 is-a 관계일 때만 써야한다. 하지만 이때도 여전히 문제가 될 수 있다. 상속의 취약점을 피하려면 상속 대신 컴포지션과 전달을 사용하자. 래퍼 클래스로 구현할 적당한 인터페이스가 있다면 더욱 그러다. 래퍼 클래스는 하위 클래스보다 견고하고 강력하다.