Search

Item 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

생성일
2023/07/27 10:36
챕터
4장 - 클래스와 인터페이스

요약

상속용 클래스를 구현하면 문서화를 철저하게 하자.
Cloneable, Serializable 인터페이스를 구현한 클래스는 상속용으로 사용하지 말자.
일반 구체 클래스는 상속용으로 설계된 것만 상속 가능하도록 막아두자.

상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는 지 문서로 남겨야한다.

재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야한다.
메서드 내부 동작 방식을 자세하게 설명해놓아야, 해당 메서드를 재정의 했을 때 어떤 영향을 받을 지 예측하기 쉬워진다.
하지만 이런 설명은 좋은 API 문서란 ‘어떻게’가 아닌 ‘무엇’을 하는지 설명해야한다는 말과 대치된다. 이는 상속이 캡슐화를 해치기 때문에, 클래스를 안전하게 상속하려면 어쩔 수 없이 API 문서를 통해 내부 구현 방식을 설명해야해서 발생하는 문제이다.

클래스 내부 동작 중 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개하기

클래스 내부 동작 과정 중에 끼어드는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개하는 방법도 있다.
java.util.AbstractList에는 removeRange를 protected 형태로 제공하는데, 이는 단지 하위 클래스에서 부분리스트의 clear 메서드를 고성능으로 만들기 쉽게하기 위함이다.

상속용 클래스는 반드시 하위 클래스 만들어보기

어떤 메서드를 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(); } }
Java
복사
위 코드는 instant를 두 번 출력하기를 기대하지만, 실제 동작 시키면 하위 클래스의 생성자가 인스턴스 필드를 초기하하기 전에 상위 클래스의 생성자가 overrideMe()를 호출하여 첫 번째는 null이 출력된다.

Cloneable, Serializable 인터페이스와 상속

Cloneable과 Serializable 인터페이스 중 하나라도 구현한 클래스를 확장하는 것은 좋지 않다.
clone과 readObject 메서드는 객체를 새로 만들기 때문에, 이를 구현할 때 생성자를 만드는 제약과 동일하게 직간접적으로 재정의 가능한 메서드를 호출해서는 안된다.
readObject 메서드의 경우에는 하위 클래스가 역직렬화 되기 전에 재정의한 매서부터 호출하게 되고, clone 메서드의 경우에는 복제본의 상태를 수정하기 전에 재정의한 메서드가 호출된다.
추가적으로 Serializable을 구현한 상속용 클래스가 readResolve나 writeReplace 메서드를 갖는다면 해당 메서드들이 하위 클래스에서 무시되지 않기 위해 private가 아닌 protected로 선언해야한다.
이렇게 많은 노력과 제약 사항들이 적용되기 때문에 가급적 상속하지 않는 것이 좋다.

일반 구체 클래스

일반 구체 클래스는 상속하여 사용하면 클래스에 변화가 생길 때마다 하위 클래스를 오작동하게 만들 여지가 있다.
때문에 상속용으로 설계하지 않은 클래스는 상속을 금지하자.
상속을 하지 못하도록 막는 방법은 다음과 같다.
final로 선언하기
private 생성자를 만들고, public 정적 팩터리 메서드를 만들어 두기