상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야 한다. 클래스의 API
로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있다. 그런데 마침 호출되는 메서드가 재정의 가능(public
과 proetected
메서드 중 final
이 아닌 모든 메서드를 뜻한다.) 메서드라면 그 사실을 호출하는 메서더의 API
에 설명을 적어야 한다. 덧붙여서 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 적어야 한다.
API
문서의 메서드 설명 끝에서 종종 Implementation Requirements
로 시작하는 절을 볼 수 있는데, 그 메서드의 내부 동작 방식을 설명하는 곳이다. 이 절은 메서드 주석에 @implSpec
태그를 붙여주면 자바독 도구가 생성해준다.
효율적인 하위 클래스를 큰 어려움 없이 만들 수 있게 하려면 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(HOOK)을 잘 선별하여 protected
메서드 형태로 공개해야 할 수도 있다. List
구현체의 최종 사용자는 removeRange
메서드에 관심이 없다. 하지만 이 메서드를 공개한 이유는 단지 하위 클래스에서 부분리스트의 clear
메서드를 고성능으로 만들기 쉽게 하기 위해서이다.
상속용 클래스를 설계할 때 어떤 메서드를 protecetd
로 노출해야 할지의 정하는 법칙은 없다. 심사숙고해서 잘 예측해본 다음, 실제 하위 클래스를 만들어 시험해보는 것이 최선이다. protected
메서드 하나하나가 내부 구현에 해당하므로 그 수는 가능한 적어야 한다.한편으로 너무 적게 노출해서 상속으로 얻는 이점마저 없애지 않도록 주의해야 한다. 상속용 클래스를 시험하는 방법은 직접 하위 클래스를 만들어 보는 것이 '유일'하다. 꼭 필요한 protected
멤버를 놓쳤다면 하위 클래스를 작성할 때 그 빈 자리를 확실히 느낄 수 있다. 거꾸로, 하위 클래스를 여러 개 만들 때까지 전혀 쓰이지 않는 protected
멤버는 사실 private
이었어야 할 가능성이 크다. 필자 경험상 이러한 검증에는 하위 클래스 3개 정도가 적당하다고 한다. 그리고 이중 하나 이상은 제 3자가 작성해봐야 한다.
널리 쓰일 클래스를 상속용으로 설계한다면 내가 문서화한 내부 사용 패턴과, protected
메서드와 필드를 구현하면서 선택한 결정에 영원히 책임져야함을 잘 알아야 한다. 이 결정들이 그 클래스의 성능과 기능에 영원한 족쇄가 될 수 있다. 그러니 상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증해야 한다.
상속용 생성자는 직접적으로 간접적으로 재정의 가능 메서드를 호출해서는 안 된다. 이 규칙을 어기면 프로그램이 오작동할 것이다. 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로 하위 클래스에 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다. 이때 그 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존한다면 의도대로 동작하지 않을 것이다. Clonealbe
과 Serializable
인터페이스는 상속용 설계를 더 어렵게 한다. 둘 중 하나라도 구현한 클래스를 상속할 수 있게 설계하는 것은 일반적으로 좋지 않은 생각이다. clone
과 readObject
메서드는 생성자와 비슷한 효과를 낸다. 따라서 이들도 위에서 말한 생성자의 제약을 따라야 한다. 마지막으로 Serializable
을 구현한 상속용 클래스가 readResolve
나 writeReplace
메서드를 갖는다면 이 메서드들은 private
이 아닌 protected
로 선언해야 한다.
상속용으로 설계하지 않는 클래스는 상속을 금지하는 것이 좋다. 상속을 금지하는 방법은 두 가지다. 클래스를 final
로 선언하거나, 모든 생성자를 private
이나 package-private
으로 선언하고 public
정적 팩터리를 만들어주는 방법이다.
클래스의 동작을 유지하면서 재정의 가능 메서드를 사용하는 코드를 제거할 수 있는 기계적인 방법이 있다. 먼저 각각의 재정의 가능 메서드는 자신의 본문 코드를 private
도우미 메서드로 옮기고, 이 도우미 메서드를 호출하도록 수정한다. 그런 다음 재정의 가능 메서드를 호출하는 ㄷ다른 코드들도 모두 이 도우미 메서드를 직접 호출하도록 수정하면 된다.