[Effective Java] 4 - item 19. 상속을 고려해 설계하고 문서화하고, 그러지 않았다면 상속을 금지해라
item 19. 상속을 고려해 설계하고 문서화하고, 그러지 않았다면 상속을 금지해라
상속을 고려한 문서화
상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
- 다시 말해, 재정의 가능한 메서드를 호출할 수 있는 모든 상황을 문서화해야 한다.
- 재정의 가능한 메서드 :
public
과protected
메서드 중final
이 아닌 모든 메서드
@implSpec
태그
- 메서드 주석에 붙여주면 자바독 도구가 내부 동작 방식을 설명하는 절을 생성해준다.
- 이 절은 “Implementation Requirements”로 시작하게 된다.
- 이 태그를 활성화하려면, 자바 프로그램 실행 시 명령줄 매개변수로
-tag "implSpec:a:Implementation Requirents:"
를 지정해주면 된다.
protected
필드 및 메서드
클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected
메서드 혹은 필드로 형태로 공개해야 할 수도 있다.
이러한 접근제한자의 결정은 직접 하위 클래스를 만들어 시험해 보는것이 유일한 해법이다.
상속용으로 설계한 클래스는 배포 전 반드시 하위 클래스를 만들어 검증해야 한다. 이 검증을 위한 하위 클래스는 3개 정도가 적당하다.
상속 클래스의 제약과 금지 방법
public class Super {
// 잘못된 예시 - 생성자가 재정의 가능 메서드를 호출한다.
public Super() {
overrideMe();
}
public void overriedMe() {
// 이 메서드는 하위 클래스에서 재정의가 가능하다.
}
}
public final class Sub extends Super {
// 초기화되지 않은 final 필드, 생성자에서 초기화한다.
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();
}
}
- 이 프로그램은 instance를 두 번 출력하지 않고, 첫 번째는
null
을 출력한다. - 상위 클래스의 생성자는 하위 클래스의 생성자가 인스턴스 필드를 초기화하기도 전에
overrideMe
를 호출하기 때문이다.
실행 순서
new Sub()
호출Super
클래스의 생성자 실행overrideMe()
호출: 하위 클래스의 생성자가 아직 실행되지 않아instant
은 초기화되지 않아null
출력Sub
클래스의 생성자 실행instant
초기화:Instant.now()
를 통해 필드 값 초기화sub.overrideMe()
호출overrideMe()
에서는 이제instant
이 필드 값이 초기화돼서 인스턴스가 출력됨
- 상속용 클래스의 생성자는 직접적/간접적 여부에 관계없이 재정의 가능 메서드를 호출해서는 안 된다.
- 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로, 하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.
- 이때, 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존한다면 의도대로 동작하지 않을 것이다. (위 예시 코드 참고)
private
,final
,static
메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.
Cloneable
과Serializable
인터페이스 둘 중 하나라도 구현한 클래스라면 상속하지 않는 것이 좋다.clone
과readObject
모두 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.
Serializable
을 구현한 상속용 클래스가readResolve
나writeReplace
메서드를 갖는다면, 이 메서드들은private
이 아닌protected
로 선언해야 한다.private
로 선언한다면 하위 클래스에서 무시되기 때문이다.
클래스를 확장해야 할 명확한 이유가 없다면, 상속을 금지하는 편이 좋다.
- 클래스를
final
로 선언한다. (더 쉬운 방법) - 모든 생성자를
private
이나package-private
으로 선언하고,public
정적 팩터리를 만들어준다.
Leave a comment