요약
•
상속용 클래스를 구현하면 문서화를 철저하게 하자.
•
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 정적 팩터리 메서드를 만들어 두기