#17 상속을 위한 설계와 문서화를 하자
이 전 글을 통해 애초에 상속을 위한 설계와 문서화가 되지 않은 클래스에서 일어날 수 있는 문제점들을 알 수 있었습니다.
이번에는 상속을 위한 설계와 문서화된 클래스에 대해 알아보겠습니다.
오버라이드 가능한 메서드에 관한 모든 것을 문서화해야 한다고 생각하면 됩니다.
내부적으로 어떤 메서드들을 호출하는지, 어떤 순서로 하는지, 호출한 결과가 어떤 영향을 주는지.
즉, 오버라이드 가능한 메서드의 모든 상황에 대해서 합니다.
문서화 내용에는 아래와 같은 내용들이 들어가야 합니다.
"This implementation(현재 구현 버전)"이라는 구문으로 시작
메서드 내부에서 처리하는 작업에 대한 설명
예를 들어, 아래 코드는 AbstractCollection 클래스의 remove 메서드입니다.
This implementation iterates over the collection looking for the specified element.
// 현재 구현 버전에서는 지정된 요소가 컬렉션이 있는지 반복해서 찾는다.
If it finds the element, it removes the element from the collection using the iterator's remove method.
// 만약 해당 요소를 찾으면 순환자(iterator)의 remove 메서드를 이용해서 삭제한다.
Note that this implementation throws an UnsupportedOperationException if the iterator returned by this collection's iterator method does not implement the remove method and this collection contains the specified object.
// 만일 이 컬렉션의 순환자 메서드에서 반환된 순환자 객체가 remove 메서드를 구현하고 있지 않으면, 현재 버전에서는 UnsupportedOperationException 예외를 발생시킵니다.
이 문서의 내용만 보면, 순환자 메서드(iterator method)를 오버 라이딩(변경) 하면 remove 메서드에 어떤 영향을 주는지 알 수 있습니다.
사실 메서드를 이용하는 입장에서는 메서드가 무슨(What) 일을 하는지가 중요하지, 어떻게(How) 일을 하는지는 중요하지는 않습니다.
하지만 상속 관계에서 서브 클래스가 안전하게 오버라이드 할 수 있게 문서화를 하려면, 어떻게 일을 하는지를 기술해야 합니다.
상속을 위한 설계 시 몇 가지 주의할 점들이 있습니다.
1. 직접 서브 클래스를 통해 테스트해봐야 합니다.
클래스 설계 시 어떤 protected 메서드나 필드를 제공해야 하는지에 대한 의문에는, 사실 정답이 없습니다.
유지보수 측면에서 protected 메서드나 필드를 최소화해야 하지만, 아예 없으면 사실상 상속의 의미가 없게 됩니다.
따라서 직접 테스트 서브 클래스를 통해 테스트해봐야 합니다.
2. 생성자에서는 오버라이드 가능한 메서드를 호출하면 안 됩니다.
슈퍼 클래스의 생성자는 서브 클래스의 생성자보다 먼저 실행됩니다.
따라서 슈퍼 클래스 생성자에서 호출한 메서드를 서브 클래스에서 오버라이드 했다면, 이 오버 라이딩된 메서드는 원하지 않은 동작을 할 수 도 있습니다.
아래 코드를 보면, 상속 관계인 Super 클래스와 Sub 클래스가 있습니다.
Super 클래스의 생성자에서 overideMe 메서드를 호출하고 있는데, 이 메서드는 Sub 클래스에서 오버라이드 하고 있습니다.
따라서 이대로 실행하면, Sub 클래스의 생성자에서 date 필드를 초기화하기 전에 overrideMe 메서드를 호출하기 때문에 "null"이 출력되고, 다음에 "현재 날짜"를 출력합니다.
이 와 같은 원치 않은 결과를 방지하기 위해서는 생성자에서 오버라이드 가능한 메서드를 호출해서는 안됩니다.
결론은 서브 클래스를 안전하게 만들 수 있도록 설계나 문서화되지 않은 클래스의 상속을 금지하는 겁니다.
상속을 금지하기 위해서는 아래와 같은 방법들을 이용할 수 있습니다.
클래스를 final로 선언
모든 생성자를 private로 선언하고, 생성자 대신 public static 팩토리 메서드를 구현
그리고 굳이 상속이 아닌 컴포지션을 통해 구현할 수 도 있으니 상속은 신중하게 이용해야 합니다.