Study/Effective-Java

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

hongeeii 2023. 11. 29. 14:59
728x90
반응형

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

재정의를 허용하는 메서드에는 해당하는 메서드에 동작원리를 설명해야 한다. = 좋은 API란 어떻게가 아니라 무엇을 해야하는지 설명을 해야한다.

제대로 문서화하지 않을 생각이라면 클래스의 상속을 금지해라.

상속을 고려한 설계와 문서화란 메서드를 재정의하면 어떤 일이 일어나는지를 정확히 정리하여 문서로 남겨야 한다는 말이다.

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

자기사용 패턴(self-use pattern)에 대해서도 문서에 남겨 다른 프로그래머에게 그 메서드를 올바르게 재정의 하는 방법을 알려야 한다.

@impleSpec

@impleSpec 태그는 자바 8에서 처음 도입되어 자바9부터 본격적으로 사용됐다.

해당 메서드와 하위 클래스 사이의 관계를 설명하여, 하위 클래스들이 그 메서드를 상속하거나 super 키워드를 이용해 호출할 때 그 메서드가 어떻게 동작하는지를 명확히 인지하고 사용하게 해야 한다.

javadoc -d {자바다큐먼트를 남길 디렉토리} {자바독을 만들 파일}

기본 자바독 명령어는 @implSpec이라는 태그를 몰라서 자바독을 만들지 못한다.

javadoc -d {자바다큐먼트를 남길 디렉토리} {자바독을 만들 파일 디렉토리} -tag "implSpec:a:Implementation Requirements:"

이렇게 해야 @impleSpec 태그를 읽어서 자바독을 생성해준다.

hook을 잘 선별하여 protected 메서드로 공개해야 한다.

상속을 위해 효율적으로 하위 클래스를 어려움 없이 만들 수 있게 하려면, 클래스의 내부 동작 과정 중간에 끼어들 수 있는 hook을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

상속 가능한 클래스를 만들었으면 하위 클래스를 하나 만들어 보는 것이다. (3개 정도는 만드는 것이 좋고 제대로 된 검증을 위해선 다른 사람이 상속받는 클래스를 만들어서 사용해보고 protected 메서드를 조정하는 것이 좋다.)

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

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

상위 클래스의 생성자는 하위클래스의 생성자보다 먼저 실행되고, 하위클래스에서 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존한다면, 의도대로 동작하지 않을 것이다.

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

    public void overrideMe() {
    }
}

///////////////////////

public final class Sub extends Super {
    // 초기화되지 않은 final 필드. 생성자에서 초기화한다.
    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();
    }
//    null
//    2023-03-22T13:39:17.827805Z
}

Cloneable, Seriallizable 인터페이스를 구현한 클래스를 상속하는 경우 비슷한 일이 생길 수 있다.

상속용으로 설계한 클래스가 아니라면 상속을 금지한다.

클래스의 상속을 금지시키는 방법

  1. 클래스를 final로 선언하기
  2. 클래스의 모든 생성자를 private이나 패키지 전용으로 하고 생성자 대신 정적 팩토리 메서드를 추가하는 것

잘된 API 문서는 what을 기술하고 how를 설명해서는 안된다는 통념이 있다.

무슨 일을 하는지 기술해야 하고 어떻게 하는지는 설명하지 말라는건데,

상속과 관련해 이러한 케이스들 같은 경우는 어쩔 수 없이 이 통념을 어기더라도

서브 클래스가 안전하게끔 클래스의 상세 구현 내역을 기술해야 한다.

OOP에서의 protected의 역할

protected는 같은 패키지 및 그 클래스를 상속(extends) 해서 구현하는 경우 접근이 가능하다.

protected는 잠재적으로 자식 클래스가 재정의해서 바꾸어야 할 경우를 고려한 modifier이다.

완성되지 못한, 완성 될 수 없는 클래스 멤버를 의미한다.

클래스를 디자인한 개발자가 이 메서드에 대해서 앞으로 더 구현할 것이 남았다거나, 혹은 디자인 컨셉으로서 일부러 완성시키지 않은 경우 둘 다를 의미할 수 있다.

abstract와 protected의 차이점

abstract는 그것을 상속하는 클래스는 반드시 해당 메서드를 구현해주어야 하는 방식이다.

이에 반해 protected는 자식클래스가 재정의를 해야 할 필요가 없다.

protected로 선언된 메서드는 재정의해서 처리해야 할 수도 있음을 클라이언트에게 알려주는 것이다.

즉 protected는 굉장히 문서적으로, 클래스 상속을 하는 개발자에게 주의를 주는 방식이다.

Reflection API는 OOP를 깨버릴 만큼 강력하다.

일반적으로 protected api는 외부 패키지에서 상속을 하지 않는 이상 사용될 수 없다고 알려져 있지만, 실제로는 Reflection API를 통해 사용이 가능하다.

Reflection API에 대한 설명 -> https://tecoble.techcourse.co.kr/post/2020-07-16-reflection-api/

728x90
반응형