이 설명에 따르면 iterator 메서드를 재정의하면 remove 메서드의 동작에 영향을 줌을 확실히 알 수 있다.
다만, 좋은 API 문서는 '어떻게'가 아닌 '무엇'을 하는지를 설명해야할텐데 위 방법은 '어떻게'를 기술하므로 캡슐화를 일부 훼손한다.
개발자가 단순하게 API가 제공하기로 약속(캡슐화)한 '무엇'을 생각없이 그대로 쓸 수 없게 된다.
다만, 안전한 상속을 위해서는 불가피하다.
상속을 고려한 설계
필요에 따라 클래스 내부 동작 과정 중간에 해당하는 부분을 protected 메서드로 공개할 때가 있다.
위 기능은 상속을 하는 이유이기도 하지만 공개해야 하는 부분을 심사숙고하여 결정하고 적절한 수준을 유지해야 한다.
위 상속용 클래스를 직접 상속하는 하위 클래스를 3가지 이상 만들어 검증, 테스트하는 방법이 유일한 테스트 방법이다.
필요한 멤버가 protected 선언이 되지 않았는지
필요없는 멤버를 아직 private로 설정하지 않았는지
테스트후 결정한 내부 사용 패턴, protected 공개 범위는 앞으로 영원히 책임져야 하는 부분임을 기억하자.
상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안된다.
// Class whose constructor invokes an overridable method. NEVER DO THIS! (Page 95)
public class Super {
// Broken - constructor invokes an overridable method
public Super() {
overrideMe();
}
public void overrideMe() {
}
}
public final class Sub extends Super {
// Blank final, set by constructor
private final Instant instant;
Sub() {
instant = Instant.now();
}
// Overriding method invoked by superclass constructor
@Override public void overrideMe() {
System.out.println(instant);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}
private, final, static 메서드는 재정의가 불가능하지 생성자에서 안심하고 호출해도 된다.
클래스를 상속용으로 설계하려면 엄청난 노력이 들고 제약도 상당하다는 것을 명심하자!!
상속 금지 방법
클래스를 final로 선언
모든 생성자를 private로 선언하고, 생성자 대신 public static 팩토리 메서드를 구현
핵심 기능을 정의한 인터페이스가 있고, 클래스가 그 인터페이스를 구현했다면 상속을 금지해도 개발하는데 아무런 어려움이 없다. #18(아이템18)에서 설명한 래퍼클래스 패턴 역시 기능을 증강할 때 상속대신 쓸수 있는 더 나은 대안.
상속을 고려한 문서화
Implementation Requirements
: API 문서에서 메서드의 내부 동작 방식을 설명@apiNote
,@implSpec
,@implNote
참고상속을 고려한 설계
필요에 따라 클래스 내부 동작 과정 중간에 해당하는 부분을 protected 메서드로 공개할 때가 있다.
상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안된다.
private, final, static 메서드는 재정의가 불가능하지 생성자에서 안심하고 호출해도 된다.
클래스를 상속용으로 설계하려면 엄청난 노력이 들고 제약도 상당하다는 것을 명심하자!!
상속 금지 방법