[이펙티브자바 - 아이템19] 상속을 고려해 설계하고 문서화하라. 그러지...

[이펙티브자바 - 아이템19] 상속을 고려해 설계하고 문서화하라. 그러지...

4장. 클래스와 인터페이스 - GOAL

추상화의 기본 단위인 클래스와 인터페이스는 자바의 심장이다.

클래스와 인터페이스를 쓰기 편하고, 견고하며, 유연하게 만들어보자

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

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

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

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

클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있다. 호출되는 메서드가 재정의 기능 메서드라면, 그 사실을 호출하는 메서드의 API 설명에 적시해야 한다. 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다. 더 넓게 말하면, 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 한다. (ex. 백그라운드 스레드, 정적 초기화 과정 등)

Implementation Requiremets 로 시작하는 절을 볼 수 있는데, 그 메서드의 내부 동작 방식을 설명하는 곳이다.

이 절은 메서드 주석에 @implSpec 태그를 붙여주면 자바독 도구가 생성해준다.

2. 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅을 잘 선별하여 protected 메서드 형태로 공개한다.

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

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

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

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

꼭 필요한 protected 멤버를 놓쳤다면, 하위 클래스를 작성할 때 그 빈자리가 확연히 드러난다.

거꾸로, 하위 클래스를 여러개 만들때까지 전혀 쓰이지 않은 protected 멤버는 사실 private 였어야 한다.

3개정도의 하위클래스가 적동하고, 이 중 하나는 제 3자가 작성해봐야한다.

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

이 규칙을 어기면 프로그램이 오동작한다.

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

import java.time.Instant; /** 상위 클래스 */ public class Super { //잘못된 예 - 생성자가 재정의 가능 메서드를 호출한다. public Super() { overrideMe(); } public void overrideMe() { } } /** 하위클래스 - override method 재정의 */ 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(); } }

위 예저는 instant를 2번 출력하지 않고, 첫번째는 null을 출력한다.

상위 클래스의 생성자는 하위 클래스의 생성자가 인스턴스 필드를 초기화하기도 전에 overrideMe를 호출하기 때문이다.

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

clone의 경우 하위 클래스의 clone메서드가 복제본의 상태를 수정하기 전에 재정의한 메서드를 호출한다. 어느쪽이든 프로그램 오작동으로 이어지게 된다.

clone이 잘못되면 원본 객체에도 피해를 줄 수 있기 때문에 조심하자.

상속을 금지하는 방법

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

클래스를 final 로 선언한다. ( 더 쉬운쪽)

로 선언한다. ( 더 쉬운쪽) 모든 생성자를 private 이나 package-private 로 선언하고 public 정적 팩터리 를 만들어주자

상속을 금지하면 사용하기에 상당히 불편해진다. 이떈, 클래스 내부에서는 재정의 가능 메서드를 사용하지 않게 만들고, 이 사실을 문서로 남기면 된다.

결론

from http://yeonni-history.tistory.com/41 by ccl(A) rewrite - 2021-10-10 00:27:46