[Effective Java] - 4장 아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

yeom yaloo·2023년 11월 20일
0

Effective Java

목록 보기
7/20

[4장 클래스와 인터페이스]

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

  • 앞서 살펴본 18장에서도 상속을 염두해 두지 않고 설계했고 상속할 때의 주의점도 문서화하지 않은 경우엔 외부 클래스가 이를 상속 받아 사용할 때의 위험을 경고했다.
  • 외부란 프로그래머의 통제권 밖에 있어 언제 어떻게 변경될지 모른다는 뜻이다.
  • 그렇다면 상속을 고려해 설계하고 문서화한 것은 어떤것인지를 이번 아이템을 통해 알아보자

[핵심 정리]

상속용 클래스 설계를 위해서는 클래스 내부에서 스스로 어떻게 사용하는지(자기 사용 패턴)를 모두 문서로 남겨야 하고 일단 문서화한 것은 클래스가 쓰이는 한 반드시 지켜야 한다.

다른 이가 효율 좋은 하위 클래스를 만들 수 있게 일부 메서드는 protected로 제공해야 할 수도 있다.

그러나 클래스를 상속 받아 확장시켜야 하는 명확한 이유가 없다면 상속은 금지시키는 편이 낫다. 상속 금지를 위해서는 클래스를 final로 선언하거나 생성자 모두를 외부에서 접근할 수 없도록 만들면 된다.

[상속을 사용해 외부 클래스를 확장하고자 한다면]

1. 부모 클래스의 메서드를 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.

  • 상속을 위해 만들어진 부모 클래스라면(상위 클래스) 해당 메서드가 내부적으로 어떻게 이용되고 있는지를 문서로 남겨야 한다.
  • 이는 자식 클래스(하위 클래스)에서 부모 클래스의 메서드를 이용해 재정의(overriding) 작업을 진행할 때 부모 클래스가 해당 메서드 안에서 또 다른 메서드를 호출하고 있을 수도 있기 때문에 이런 내부 이용 방식을 상세히 문서화 시켜야 한다.
  • 앞서 살펴본 아이템 18에서 add메서드를 사용해서 원소 개수를 세는 메서드를 재정의 했다. 이때 부모 메서드에서 해당 작업을 이미 또 add() 하고 있어서 3번이 더해져야 하는 것이 6번이나 더해진 경험이 있었다. 이 예시를 떠올리며 문서화 해야하는 이유를 마음에 각인 시키자.

2. 재정의 가능 메서드는 모두 문서로 남기자

  • 재정의 가능 메서드란 public과 protected 메서드 중 final이 아닌 모든 메서드를 의미한다.
  • 재정의 가능 메서드는 호출할 수 있는 모든 상황에 대비해서 문서화를 시켜 놓아야 한다

3. 메서드의 내부 동작을 설명하는 Implementation Requirements

  • 메서드 내부 동작 방식을 설명하는 Implementation Requirements는 종종 API 문서 메서드 설명의 끝부분에 종종 존재한다.
  • 해당 내용을 생성하고 싶다면 @implSpec 태그를 사용하자, 자바독 도구가 메서드 내부 동작 방식에 관한 설명을 생성해준다.

내부 매커니즘 문서화 이외의 방식

1. 클래스 내부 동작 과정 중간에 끼어들 훅을 잘 선별해 protected 메서드 형태로의 공개

  • AbstractList의 removeRange는 protected로 공개되어있다.
  • 이 경우에 List 구현체는 해당 메서드엔 관심이 없지만 공개가 되어 있는 이유는 clear 메서드를 고성능으로 만들기 위함이다.

널리 쓰일 상속용 클래스 설계

1. 상속용 클래스 배포 전 반드시 하위 클래스를 만들어 검증할 것

2. 상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출하면 안 된다.

  • 이 경우는 상위 클래스 생성자가 하위 클래스의 생성자보다 먼저 실행된다.
  • 때문에 하위 클래스에서 재정의한 메서드가 하위 클래스 생성자보다 먼저 호출되기 때문이다.
  • 이 문제는 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존하는 경우에 의도대로 동작하지 않을 것이다.
  • 재정의 가능한 메서드? final 키워드가 붙지 않은 public protected의 접근 제한자를 가지고 있는 메서드를 의미한다.

    [부모 클래스 생성자에서 마음 놓고 호출해도 되는 메서드?]
    이때 그렇다면 private, final, static의 경우엔 메서드 재정의가 불가하기 때문에 부모 클래스 생성자에서 마음 놓고 해당 메서드를 호출해도 된다.


public class Super {
    // 잘못된 예시 - 생성자가 재정의 가능 메서드를 호출하고 있다.
    public Super() { // 3
        overrideMe(); // 4
    }

    public void overrideMe() {

    }
}

package christmas;

import java.time.Instant;

public final class Sub extends Super {

    private final Instant instant;

    Sub() { // 2
        this.instant = Instant.now(); // 6 ->  2023-11-20 하는 형식의 now값
    }


    //상위 클래스의 생성자가 해당 메서드를 호출!
    @Override
    public void overrideMe() { // 5 -> null 출력
        System.out.println(instant); 
    }

    public static void main(String[] args) {
        Sub sub = new Sub(); // 1
        sub.overrideMe(); // 7 -> 2023-11-20 하는 형식의 now값 출력
    }
}

  • 위의 숫자를 보면서 상속에서 재정의 가능 메서드를 호출할 때 어떤 흐름으로 해당 메서드가 호출되는지 확인하고 왜 우리는 상속하는 클래스의 생성자에서 재정의 가능 메서드를 호출하지 말아야 할지를 알아보자

3. clone과 readObject는 모두 직접적, 간접적으로 모두 재정의 가능 메서드 호출을 해선 안된다.

4. 일반 구체 클래스의 경우도 예외는 아니다.

  • 상속을 하지 않을 예정인 구체 클래스라도 상속을 하지 않을 예정이라면 상속을 하지 못하게 막는게 중요하다.
  • 가장 쉬운 방법은 클래스를 final 선언 하는 것이다.
  • 두 번째 선택지는 모든 생성자를 private, package-private로 선언하는 것이다.
  • 세 번째는 public 정적 팩터리를 만드는 것이다.
profile
즐겁고 괴로운 개발😎

0개의 댓글