상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
내부 매커니즘을 문서로 남기는 것만이 상속을 위한 설계의 전부는 아니다. 효율적인 하위 클래스를 큰 어려움 없이 만들 수 있게 하려면 클래스의 내부 동작 과정 중 끼어들 수 있는 훅(hook)을 잘 선별해서 protected 메서드 형태로 공개해야 할 수도 있다.
어떤 메서드를 protected로 노출할 지는 심사숙고해서 잘 결정한 후 하위 클래스를 만들어 테스트를 해보자. 다만 protected 메서드는 내부 구현이므로 그 수는 가능한 한 적어야 한다.
상속용 클래스를 테스트하는 방법은 직접 하위 테스트를 만들어 보는 것이 유일하다. 꼭 필요한 protected 멤버를 놓쳤다면 하위 클래스를 작성할 때 그 빈자리가 확연히 드러난다.
상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안된다.
public class Super {
public Super() {
overrideMe();
}
public void overrideMe() {
}
}
public final class Sub extends Super {
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();
}
}
상위 클래스의 생서자는 하위 클래스의 생성자보다 먼저 실행되기 때문에 하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.
Cloneable 과 Serializable 를 상속한 클래스를 상속하는 것은 더 어려운 일이다. readObject 의 경우 하위 클래스의 상태가 미처 다 역직렬화 되기 전에 재정의한 메서드부터 호출한다. clone 의 경우 하위 클래스의 clone 메서드가 복제 본의 상태를 수정하기 전에 재정의한 메서드를 호출한다. 이는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출하지 마라는 제약에 걸린다.
상속은 캡슐화를 깨뜨릴 수 있는 문제가 있다. 그렇다면 상속용으로 설계하지 않은 클래스의 상속은 어떻게 막아야 할까? 가장 좋은 방법은 상속을 금지시키는 것이다. 두 가지 방안이 있다.
첫째는, 클래스를 final 로 선언하는 방법이다. 둘째는, 모든 생성자를 private 나 package-private로 선언하고 public 정적 팩터리 메서드를 만들어주는 방법이다.
핵심 기능을 정의한 인터페이스와 구체 클래스가 이 인터페이스를 확장했다면 상속을 금지해도 어려움이 없다. 하지만, 표준 인터페이스를 구현하지 않았는데 상속을 금지했다면 사용이 불편해진다. 이럴때는 클래스 내부에서 재정의 가능 메서드를 사용하지 않게 만들고 이 사실을 문서로 남기면 된다.
public class A{
public A(){};
public void OverrideMethod(){
OverrideHelperMethod();
}
private void OverrideHelperMethod(){
...(OverridMethod() 였던 것)
}
}
public class A extends B{
public B(){
}
@Override
public void OverrideMethod(){
}
}
클래스의 동작을 유지하면서 재정의 가능 메서드를 사용하는 코드를 제거할 수 있는 기계적인 방법은 다음과 같다.