[아이템19] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
이번 아이템 19를 학습하면서 제일 기억에 남는 문구는 이거다.
"상속용 클래스를 설계하기란 결코 만만치 않다."
상속을 위한 설계에는 다음과 같은 것들이 있다.
외부 클래스를 상속할 때 위험성은
프로그래머의 통제권 밖에 있어서 언제 어떻게 변경될지 모른다는 것이다.
그렇기 때문에
책에서는 메서드를 재정의하면 어떤 일이 일어나는지 문서화하라고 명시하고 있다.
✅ 문서화
설계 및 문서화 대상 : 상속용 클래스
재정의 가능한 것들 : public, protected 메서드 중 final이 아닌 모든 메서드
문서화 방법
메서드 주석에 @implSpec 태그를 붙여주자!
- API로 공개된 메서드에서 자신의 또 다른 재정의 가능 메서드를 호출할 경우
- 어떤 순서로 호출하는지
- 호출 결과가 이어지는 처리에 어떤 영향을 주는지
- 재정의 가능 메서드를 호출할 수 있는 모든 상황
✅ protected 메서드 제공
"문서로 남기는 것만이 상속을 위한 설계의 전부는 아니다. ...
클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여
protected 메서드 형태로 공개해야 할 수도 있다."
즉, 필요한 부분을(hook이 되겠지요) protected 메서드로 정의해 범위를 늘리게 되면
내부 구현이 노출되므로 가능한 적게, 하지만 상속의 이점을 살릴 수 있도록 너무 적지 않게 제공해야겠다.
"꼭 필요한 protected 멤버를 놓쳤다면 하위 클래스 작성시 빈자리가 드러날 것이고,
하위 클래스를 여러 개 만들 때까지 전혀 쓰이지 않는 protected 멤버는 사실
private이었어야 할 가능성이 크다."
"상속용 클래스를 설계할 때 어떤 메서드를 protected로 노출해야할지 어떻게 결정할까?"
직접 하위 클래스를 만들어보는 것이 유일!
하위 클래스는 3개 정도가 적당하고 이 중 하나 이상은 제 3자가 작성해보는 것이 좋다.
👉 이렇게 검증과정을 거쳐 훅의 개수를 정할 수 있다.
상속을 허용하는 클래스가 지켜야할 제약 사항
✅ "... 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다."
상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로
하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출되기 때문이다.
그렇기 때문에 재정의가 불가능한 private, final, static 메서드만 호출해야한다.
public class Super{
public Super(){
overrideMe();
}
public void overrideMe(){ }
}
public class Sub extends Super{
private final Instant instant;
public Sub(){
this.instant = Instant.now();
}
@Override
public void overrideMe(){ //Super의 overrideMe를 재정의
System.out.println(instant);
}
}
일반 클래스를 안전하게 상속할 수 있는 방법
"... 상속용으로 설계하지 않은 클래스는 상속을 금지하는 것이다."
하지만
구체 클래스가 표준 인터페이스를 구현하지 않았는데 상속을 금지하면 사용하기에 상당히 불편해진다.
클래스의 동작을 유지하면서 재정의 가능 메서드를 사용하는 코드를 제거할 수 있는 기계적인 방법
"각각의 재정의 가능 메서드의 본문 코드를 private 도우미 메서드로 옮기고,
이 도우미 메서드를 호출하도록 수정한다."
상속을 금지하는 방법
✅ 클래스를 final로 선언
✅ 모든 생성자 private, package-private 선언 후, public 정적 팩토리 메서드 생성
public class FactoryTest{
private FactoryTest(){ } //생성자 private으로 막기(상속x)
public static FactoryTest valueOf(){ //정적 팩토리 메서드 통해서만 클래스 사용 가능
return new FactoryTest();
}
}