[Effective Java] 아이템 19 - 상속을 고려해 설계하고 문서화하라

HyeBin, Park·2022년 6월 13일
0

Effective Java Study

목록 보기
15/20
post-thumbnail

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

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

상속을 고려한 설계와 문서화란 뭘까?

  • 메서드를 재정의하면 어떤 일이 일어나는지를 정확히 정리하여 문서로 남겨야한다.

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

  • 클래스의 API로 공개된 메서드에서 호출되는 메서드가 재정의 가능 메서드라면 호출하는 메서드 API 설명에 적시해야한다.

  • 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다.

  • 재정의 가능이란 public과 protected 메서드final이 아닌 모든 메서드를 뜻한다.
    => 더 넓게 말하면 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 한다.

@implSpec

  • 자바 8에서 처음 도입되어 자바 9부터 본격적으로 사용되기 시작했다.
  • 활성화 방법은 -tag "implSpec:a:Implementaion Requirements:"를 지정해주면 된다.
  • 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

<protected 메서드 형태로 공개해야 하는 예시>

  • List 구현체의 최종 사용자는 removeRange 메서드에 관심이 없지만 하위 클래스에서 부분리스트의 clear 메서드를 고성능으로 만들기 쉽게 하기 위해서 제공한다.

  • removeRange 메서드가 없다면 하위 클래스에서 clear 메서드를 호출하면 제거할 원소 수의 제곱에 비례해 성능이 느려지거나 부분리스트의 메커니즘을 밑바닥부터 새로 구현해야 한다.

protected로 노출해야할지는 어떻게 결정하는가?

  • 심사숙고해서 잘 예측해본다음, 실제 하위 클래스를 만들어 시험해보는 것이 최선
  • protected 메서드 하나하나가 내부 구현에 해당하므로 그 수는 가능한 한 적어야 한다.
  • 한편으로는 너무 적게 노출해서 상속으로 얻는 이점마저 없애지 않도록 주의

상속용 클래스를 시험하는 방법은 직접 하위 클래스를 만들어보는 것이 유일

  • 꼭 필요한 protected 멤버를 놓쳤다면 하위 클래슬르 작성할 때 빈자리가 드러나고
  • 하위 클래스를 여러 개 만들 때까지 전혀 쓰이지 않은 protected 멤버는 private이었어야 할 가능성이 크다
  • 저자의 경험상 검증에는 하위 클래스 3개 정도가 적당하고, 이 중 하나 이상은 제 3자가 작성해봐야 한다.

상속을 허용하는 클래스가 지켜야 할 제약

  • 상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.
    => 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로 하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.

  • instant 두 번 출력하리라 기대했겠지만, 첫 번째는 null을 출력한다.
  • 상위 클래스의 생성자는 하위 클래스의 생성자가 인스턴스 필드를 초기화하기도 전에 overrideMe를 호출한다.
  • final 필드의 상태가 프로그램에서는 두 가지인데 정상이라면 단 하나뿐이어야 한다.
  • 상위 클래스의 생성자가 overrideMe를 호출할 때 NullPointerException을 던지고 println이 null 입력을 받아 null을 출력한다.

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

  • readObject의 경우 하위 클래스의 상태가 미처 다 역직렬화되기 전에 재정의한 메서드부터 호출하게 된다.
  • clone의 경우 하위 클래스의 clone 메서드가 복제본의 상태를 수정하기 전에 재 정의한 메서드를 호출한다.
    => 둘 다 프로그램 오작동으로 이어질 것이다. 특히 clone은 원본에도 피해를 줄 수 있다.
  • Serializable을 구현한 상속용 클래스가 readResolve나 write Replace 메서드를 갖는다면 이 메서드들은 private이 아닌 protected로 선언해야한다.
    => private 선언 시 하위 클래스에서 무시됨, 상속을 허용하기 위해 내부 구현을 클래스 API로 공개하는 예 중 하나

상속을 금지하기

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

  • 클래스를 final로 선언하기
  • 모든 생성자를 private나 package-private로 선언하고 public 정적 팩터리를 만들어 주는 방법

0개의 댓글