상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 함
클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있음
호출되는 메서드가 재정의 가능 메서드라면, 그 사실을 호출하는 메서드의 API 설명에 적시
어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지 정리
‘재정의 가능’: public, protected 메서드 중 final이 아닌 모든 메서드를 뜻함
재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 함
ex) 백그라운드 스레드, 정적 초기화 과정에서도 호출이 일어날 수 있음
@implSpec 태그
API 문서의 메서드 설명 끝에 보이는 “Implementation Requirements”로 시작하는 절
해당 메서드의 내부 동작 방식을 설명하는 곳임.
해당 태그를 붙여주면 자바독 도구가 생성해준다
이처럼 내부 메커니즘을 문서로 남기는 것만이 상속을 위한 설계의 전부는 아님
클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별해 protected 메서드 형태로 공개
어떤 메서드를 protected로 노출? → 잘 예측한 후 실제 하위 클래스를 만들어 시험해본다
상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증해야 함
상속용 클래스의 생성자는 직접적, 간접적 모두 재정의 가능 메서드를 호출하면 안된다.
상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로,
하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.
// 잘못된 예 - 생성자가 재정의 가능 메서드를 호출함
// 상위 클래스 코드
public class Super {
public Super() {
overrideMe();
}
public void overrideMe() {
}
}
// 하위 클래스 코드
public final class Sub extends Supper {
private final instant instant;
Sub() {
instant = Instant.now();
}
@Override public void overrideMe() {
System.out.println(instant);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}
위 코드는 instant를 두 번 출력하지 않고, 첫번째에서는 null을 출력함
상위 클래스의 생성자는 하위 클래스의 생성자가 인스턴스 필드를 초기화하기도 전에 overrideMe를 호출하기 때문
상속용으로 설계하지 않는 클래스는 상속을 금지한다.
상속을 금지하는 방법
클래스를 final로 선언
모든 생성자를 private이나 package-private으로 선언 후 public 정적 팩터리를 만들어준다
3줄 요약
상속용 클래스를 설계하려면 클래스 내부에서의 자기사용 패턴을 모두 문서로 남겨야 하며, 문서화된 것은 그 클래스가 쓰이는 한 반드시 지켜져야 한다. 그렇지 않으면 하위 클래스들이 오동작한다.
상속을 고려한 문서화
메서드 재정의시 발생하는 일을 정확히 정리
상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 함
클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있음
@implSpec 태그
이처럼 내부 메커니즘을 문서로 남기는 것만이 상속을 위한 설계의 전부는 아님
상속용 클래스의 생성자는 직접적, 간접적 모두 재정의 가능 메서드를 호출하면 안된다.
상속용으로 설계하지 않는 클래스는 상속을 금지한다.
3줄 요약