🤓 스터디/이펙티브 자바

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

강켄트 2023. 3. 31. 01:37

 이번 아이템 19를 학습하면서 제일 기억에 남는 문구는 이거다.

"상속용 클래스를 설계하기란 결코 만만치 않다."

 

상속을 위한 설계에는 다음과 같은 것들이 있다.

 

외부 클래스를 상속할 때 위험성은

프로그래머의 통제권 밖에 있어서 언제 어떻게 변경될지 모른다는 것이다.

그렇기 때문에

책에서는 메서드를 재정의하면 어떤 일이 일어나는지 문서화하라고 명시하고 있다.

문서화

설계 및 문서화 대상 : 상속용 클래스

재정의 가능한 것들 : public, protected 메서드 중 final이 아닌 모든 메서드  

 

문서화 방법 

메서드 주석에 @implSpec 태그를 붙여주자!

  • API로 공개된 메서드에서 자신의 또 다른 재정의 가능 메서드를 호출할 경우 
  • 어떤 순서로 호출하는지 
  • 호출 결과가 이어지는 처리에 어떤 영향을 주는지 
  • 재정의 가능 메서드를 호출할 수 있는 모든 상황

 

protected 메서드 제공

"문서로 남기는 것만이 상속을 위한 설계의 전부는 아니다. ... 

클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여

protected 메서드 형태로 공개해야 할 수도 있다." 

 

즉, 필요한 부분을(hook이 되겠지요) protected 메서드로 정의해 범위를 늘리게 되면

내부 구현이 노출되므로 가능한 적게, 하지만 상속의 이점을 살릴 수 있도록 너무 적지 않게 제공해야겠다.

 

"꼭 필요한 protected 멤버를 놓쳤다면 하위 클래스 작성시 빈자리가 드러날 것이고,

하위 클래스를 여러 개 만들 때까지 전혀 쓰이지 않는 protected 멤버는 사실 

private이었어야 할 가능성이 크다."

 

"상속용 클래스를 설계할 때 어떤 메서드를 protected로 노출해야할지 어떻게 결정할까?"

직접 하위 클래스를 만들어보는 것이 유일!

하위 클래스는 3개 정도가 적당하고 이 중 하나 이상은 제 3자가 작성해보는 것이 좋다.

👉 이렇게 검증과정을 거쳐 훅의 개수를 정할 수 있다.

 

 

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

✅ "... 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다."

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

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

그렇기 때문에 재정의가 불가능한 private, final, static 메서드만 호출해야한다.

 

public class Super{

   public Super(){
      overrideMe();
   }
   
   public void overrideMe(){ }
}
public class Sub extends Super{
   private final Instant instant;
   
   public Sub(){
      this.instant = Instant.now();
   }
   
   @Override
   public void overrideMe(){ //Super의 overrideMe를 재정의
      System.out.println(instant);
   }
}

 

 

일반 클래스를 안전하게 상속할 수 있는 방법 

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

 

하지만 

구체 클래스가 표준 인터페이스를 구현하지 않았는데 상속을 금지하면 사용하기에 상당히 불편해진다.

 

클래스의 동작을 유지하면서 재정의 가능 메서드를 사용하는 코드를 제거할 수 있는 기계적인 방법

"각각의 재정의 가능 메서드의 본문 코드를 private 도우미 메서드로 옮기고, 

이 도우미 메서드를 호출하도록 수정한다."

 

상속을 금지하는 방법

✅ 클래스를 final로 선언 

✅ 모든 생성자 private, package-private 선언 후, public 정적 팩토리 메서드 생성

public class FactoryTest{
   private FactoryTest(){ } //생성자 private으로 막기(상속x)
   
   public static FactoryTest valueOf(){ //정적 팩토리 메서드 통해서만 클래스 사용 가능
      return new FactoryTest();
   }
}