■ 상속의 위험성
상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서라면 상속도 안전하다. 하지만 일반적인 구체 클래스를 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다.
- 상속은 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
- 상위 클래스의 릴리스마다 내부 구현이 달라질 수 있으므로 하위 클래스가 오동작할 수 있다.
- 자신의 다른 부분을 사용하는 '자기 사용'여부는 해당 클래스의 내부 구현에 해당 되며 다음 릴리스에서도 유지될 수 알 수 없다.
- 하위 클래스에 추가한 새 메서드가 상위 클래스 다음 릴리즈에서 같은 시그니처를 가질 경우 컴파일도 되지 않는다.
■ 컴포지션 설계
상속의 문제점을 피하기 위한 방법으로 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하는 방법이 있다. 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻에서 이러한 설계를 컴포지션(구성)이라 한다.
- 컴포지션(composition) : 기존 클래스가 새로운 클래스의 구성 요소로 쓰인다. (private)
- 전달(forwarding) : 새 클래스의 인스턴스를 참조하는 메서드들은 기존 클래스의 대응하는 메서드를 호출해 그 결과를 반환한다.
- 전달 메서드(forwarding method) : 새 클래스의 메서드
래퍼 클래스 - 상속 대신 컴포지션 사용
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(); }
}
■ 상속 원칙
상속은 반드시 하위 클래스가 상위 클래스의 '진짜' 하위 타입인상황에서만 사용해야한다. 즉, 클래스 B가 클래스 A와 is-a 관계일 때만 클래스 A를 상속해야한다. 대답이 아닐 경우 A를 private 인스턴스로 두고, A와는 다른 API를 제공해야하는 상황이 대다수다.
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템20 추상 클래스보다는 인터페이스를 우선하라 (0) | 2021.02.24 |
---|---|
[Effective Java] 아이템19 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2021.02.23 |
[Effective Java] 아이템17 변경 가능성을 최소화하라 (0) | 2021.02.17 |
[Effective Java] 아이템16 public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2021.02.17 |
[Effective Java] 아이템15 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2021.02.17 |