본문 바로가기

Dev Book Review/Effective Java

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

1. 재정의 메서드의 문서화

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

  • 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의 호출 결과 처리 영향 기술
  • 호출할 수 있는 모든 상황을 남긴다.
    백그라운드 스레드, 정적 초기화 과정에서 호출이 일어날 수 있는 상황 등

API 문서의 Implementation Requirements : 메서드의 내부 동작 방식 설명

  • 메서드 주석에 @ImplSpec 태그를 붙여주면 자바독 도구가 생성된다.

좋은 API문서란 '어떻게'란 '무엇'을 하는지를 설명해야한다.

  • 대치된다 : 그러나 상속이 캡슐화를 해치기 때문에 어쩔수 없다 - 클래스를 안전하게 상속할 수 있게 해야하기 때문

 

2. 상속 설계 : 내부 동작과정 훅 선별

클래스의 내부 동작과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

ex ) Clear 메서드를 호출할 때 성능을 위해 사용되는 protected removeRange() 메서드

실제 하위 클래스를 만들어 시험해본 뒤, protected 메서드의 내부 노출 정도를 결정한다.
너무 적게 노출해서 상속으로 얻는 이점마저 없애지 않게 주의한다.

상속용 클래스의 시험 방법 : 직접 하위클래스를 만들어본다.

하위클래스 여러개 만들때까지 전혀 쓰지않는 protected 멤버는 private으로 돌린다.

배포전에 반드시 클래스를 만들어 검증하자.

 

3. 생성자에서 재정의 가능 메서드 호출 금지

상속용 클래스의 생성자는 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로
  • 재정의한 메서드가 하위 클래스의 생성자에서 초기화한 값에 의존하면 의도대로 동작하지 않는다.
  • private, final, static 메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.

 

4. Cloneable, Serializable 인터페이스는 상속에서 닫아두자

확장하려는 프로그래머에게 엄청난 부담을 준다.

clone과 readObject 메서드는 생성자와 비슷한 효과를 낸다 (새로운 객체를 만든다.)
따라서 상속용 클래스에서 구현할 때 제약도 생성자와 비슷하다.

clone과 readObject 모두 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • readObject : 하위 클래스가 미처 역직렬화 되기 전에 재정의한 메서드부터 호출한다.
  • clone : 하위 클래스의 clone 메서드가 복제본의 상태를 수정하기 전에 재정의한 메서드를 호출 한다.
    원본 객체에도 피해를 줄 수 있다. (clone이 완벽하지 못해 원본 객체의 참조가 있을 경우)

 

5. Serializable을 구현한 상속용 클래스 주의

readResolve나 writeReplace 메서드를 갖는다면 이 메서드는 private이 아닌 protected로 선언해야한다.

private으로 선언하면 하위 클래스에서 무시된다 : 상속 허용을 위해 내부 구현을 클래스 API로 공개

 

6. 상속에 대한 조언

a. 상속용으로 설계하지 않은 클래스는 상속을 금지한다.

  • 클래스를 final로 선언하자.
  • 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어준다.

b. 구현상속보단 인터페이스 상속을 사용하는 것이 더 나은 내안이다.

c. 상속을 허용해야겠다면 재정의 가능 메서드를 사용하지 않게 만들고 문서로 남긴다.

d. 클래스 동작 유지 + 재정의 가능 메서드 사용 코드 제거

  • 재정의 가능 메서드는 자신의 본문 코드를 private 도우미 메서드로 옮긴다.
  • 그리고 이 도우미 메서드를 호출한다.
public add(){
  add();
}
private add(){
  ...
}