본문 바로가기
우아한테크코스/공부

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

by 자바지기 2022. 3. 15.
반응형

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

  • 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
  • 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야한다.
  • 메서드 설명을 작성하기위해 @implSpec 태그를 붙여주면 자바독 도구가 생성해준다.

 

다음은 java.util.AbstractCollection에서 찾은 예

위와 같이 메서드 설명을 통해서 내부 동작 방식을 알 수 있다.

 

이렇게 작성하는 것은 상속이 캡슐화를 해치기 때문이다.

클래스를 안전하게 상속할 수 있도록 하려면 내부 구현 방식을 설명해야만 한다.

 

문서를 남기는 것 뿐만 아니라 상속을 위해 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

 

hook이란?

추상 클래스에 들어있는, 아무 일도 하지 않거나 기본 행동을 정의하는 메소드로, 서브 클래스에서 오버라이드 할 수 있다.

 

이 때 어떤 메서드를 protected로 노출해야할 지는 어떻게 결정할까?

실제 하위 클래스를 만들어 시험해보는 것이 유일하다.

하위 클래스를 여러 개 만들 때까지 전혀 쓰이지 않는 protected 멤버는 private로 작성해야할 가능성이 크다.

 

상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증하자.

 

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

상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로,

하위 클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 호출된다.

아래는 이것이 문제가 되는 예시이다.

 

결과가 어떻게 나올지 예측해보자.

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
2022-03-15T07:55:13.396346Z

 

Sub 생성자가 호출될 때 Super의 생성자가 먼저 실행된다.

Super에 존재하는 overrideMe 메서드는 Sub에서 재정의된 overrideMe를 실행한다.

이 때 인스턴스 필드가 초기화되기 이전이므로 null이 출력된다.

 

이런식의 문제가 발생할 수 있다.

 

 

이러한 문제들을 해결할 수 있는 방법은

 

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

 

상속을 금지하는 방법

1. 클래스를 final로 선언

2. 모든 생성자를 private나 package-private로 선언하고 public 정적 팩터리를 만들어준다.

 

그래도 상속을 써야겠다면?

재정의 가능 메서드를 호출하는 자기 사용 코드를 완벽히 제거해라

 

 

 

 

반응형

댓글