규칙 17. 계승을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 계승을 금지하라

계승을 위한 설계와 문서를 갖춘다는것은 무슨의미일까?

메서드를 재정의 하면 어떤일이 생기는 지 문서에 잘 남겨야 한다. 즉 재정의 가능 메소드가 내부적으로 어떻게 동작하는지 문서에 남기라는 것이다. public, protected로 선언된 모든 메서드와 생성자에 대해 어떤 재정의 가능 메서드를 어떤 순서로 호출하는지 호출결과는 어떤 영향을 미치는지 남기라는 것이다.
예를들어 후면 스레드가 호출할 수도 있고 static 초기화 구문 안에서 호출할 수도 있다. 관습적으로는 재정의 가능 메서드를 어떤식으로 호출하는지 주석 마지막에 명시한다.

좋은 API 문서는 메서드가 하는 일이 무엇인지 명시하지 어떻게 하는지 명시하면 안되지 않아?

그렇다. 이는 맞는말이다. 계승이 캡슐화 원칙을 침해하기 때문에 발생하는 결과로 볼 수 있다. 문서만 제대로 썼다고 계승에 적합한 설계가 되지는 않는다. 너무 애쓰지 않고도 효율적인 하위클래스를 정의하기 위해서는 클래스 내부 동작에 개입할 수 있는 훅을 신중하게 고른 protected 메서드 형태로 제공해야 한다.

그렇다면 클래스를 설계할 때 protected로 선언할 멤버는 어떻게 정해?

딱히 정해진 방법은 없다. 생각하고 신중하게 고른 다음 실제로 하위 클래스를 만들어 보면서 테스트하는것이 최선이다. protected 멤버 개수는 가능한 한 줄여야 하는데 구현 세부사항에 대한 일종의 서약 구실을 하기 떄문이다.
즉 계승을 위해 설계할 클래스를 테스트할 유일한 방법은 하위 클래스를 직접 만들어 보는것이다. 만일 중요한 멤버를 protected로 선언하는것을 잊었다면 하위클래스에서 고통스러울 수 있다. 반대로 하위 클래스를 몇개 만들어봐도 사용할 일이 없는 protected 멤버는 private로 선언해야 한다.

그렇게 까지 신중하게 정해야하나?

널리 사용될 클래스를 계승에 맞게 설계할 때는 문서에 명시한 내부 호출 패턴 뿐 아니라 메서드와 필드를 protected로 선언하는 과정에 함축된 구현 관련 결정들을 영원히 고수해야 한다는 점을 기억해야 한다. 따라서 다음 릴리스에 성능이나 기능을 개선하기 어려워진다.
또한 계승용 문서는 API문서를 어지럽힐 수 있다.

계승을 허용하기 위해 따라야 할 제약사항.

1.생성자는 직접적이건 간접적이건 재정의 가능 메서드를 호출해서는 안된다.
이유: 상위클래스 생성자는 하위클래스의 생성자보다 먼저 실행되므로, 하위클래스에서 재정의한 메서드는 하위클래스 생성가가 실행되기 전엔 호출될 것이다. 재정의한 메서드가 하위 클래스 생성자가 초기화한 결과에 의존할 경우 그 메서드는 원하는대로 실행되지 않을것이기 떄문에.

2.계승용 클래스가 Cloneable과 Serializable 와 같은 인터페이스를 구현하도록 하는것은 바람직하지 않다.
이유: 클래스를 계승할 프로그래머에게 과도한 책임을 지우기 때문이다. 하지만 하위 클래스 프로그래머가 이들 인터페이스를 선택적으로 구현할 수 있도록 하는 특별한 방법들이 있는데, 이런 조치들에 대해서는 규칙 11, 규칙 74에서 설명한다.
만약 계승용 클래스가 Cloneable과 Serializable 와 같은 인터페이스를 구현해야 한다면 clone이나 readObject 메서드 안에서 직접적이건 간접적이건 재정의 가능한 메서드를 호출하지 않도록 주의해야 한다.

3.Serializable 인터페이스를 구현하는 계승용 클래스에 readResolve와 writeReplace 메서드가 있다면 priavte가 아닌 protected로 선언해야 한다.
이유 : private로 선언해 버리면 하위 클래스는 해당 메서드들을 조용히 무시하게 된다. 이것은 계승을 허용하기 위해 구현 세부사항을 클래스 API의 일부로 포함시켜야 하는 사례 가운데 하나다.

정리

계승에 맞도록 설계하고 문서화 하지 않은 클래스에 대한 하위클래스는 만들지 말라.
가장 쉬운 방법은 클래스를 final로 선언하거나 모든 생성자를 private나 package-private로 선언하고 생성자 대신 public 정적 팩터리 메서드를 추가하는것. 규칙 15에서 설명한 대로 이방법을 사용하면 클래스나 패키지 내부에서는 하위 클래스를 만들어 쓸수도 있으므로 편리하다.
계승을 반드시 허용해야 한다고 느껴지면 재정의 가능 메서드는 절대로 호출하지 않도록 하고 그 사실을 반드시 문서에 남겨라.

Share