본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 19 - 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 - 핵심 정리

by 개발인생 2022. 11. 25.
반응형

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

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

상속용 클래스는 내부 구현을 문서로 남겨야 한다.

상속 대신 컴포지션 을 사용하라고 했지만 상속은 객체지향의 가장 큰 특징이자

여러가지 코드를 재사용하는 좋은 방법 중 하나이기 때문에 어떻게하면 상속을 잘 사용할 수 있을지에 대한 아이템이다.

상속을 허용할거라면 문서화 를 해야한다.

상속용 클래스에스 재정의를 허용 하는 메서드에는 해당 메서드에 내부 동작 원리를 문서화해야한다.

좋은 API 는 어떻게가 아니라 무엇을 해야하는지에 대해 설명해야한다 라는 격언과 반대되는 말이다.

문서를 읽었을 떄 해당하는 클래스가 하는 역활을 파악할 수 있어야 좋은 문서이다.

내부 동작 원리를 구체적으로 설명하면 그 동작 원리에 묶이게 되므로 좋은 문서가 아니다.

상속을 허용하는 클래스에서 재공하는 재정의를 허용하는 메서드에서는 어떤식으로 동작하는지 구체적으로 문서화 를 해야한다.

해당 클래스를 상속해서 재정의할 때 구체적인 동작을 알아야하기 때문이다.

상속을 허용함으로써 캡슐화 가 꺠지기 때문에 내부 구현을 상세히 알아야한다.

/**
 * Example class for java documentation for extendable class
 */
public class ExtendableClass {

    /**
     * This method can be overridden to print any message.
     *
     * @implSpec
     * Please use System.out.println().
     */
    protected void doSomething() {
        System.out.println("hello");
    }
}

재정의를 허용할 메서드에 @implSpec 태그를 이용해 문서를 작성한다.

@implSpec 태그는 자바8 부터 사용되었다.

@implSpec 태그에 동작 원리를 추가한다.

javadoc 을 생성할 때 -tag "implSpec:a:Implemetation Requirements: 옵션을 추가해야한다.

-tag <태그이름>:<위치>:<변경할 태그이름> 의 형식이다.


내부 동작 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드로 공개해야 한다.

재정의를 허용할 메서드를 잘 선별해야한다는 의미이다.

재정의할 메서드를 protected 로 만들어야한다.


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

상속을 가능한 클래스를 만들었으면 서브 클래스를 직접 만들어봐야한다.

최소한 3개를 만들것을 권장하며 그 중 한개는 본인이 아닌 다른 개발자를 통해 만들도록 권장한다.

필요한 대로 잘 만들어졌는지 검증하면서 protected 메서드를 조정한다.


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

// 재정의 가능 메서드를 호출하는 생성자 - 따라 하지 말 것! (115쪽)
public class Super {
    // 잘못된 예 - 생성자가 재정의 가능 메서드를 호출한다.
    public Super() {
        overrideMe();
    }

    public void overrideMe() {
    }
}

상속을 허용할 Super 클래스이다.


// 생성자에서 호출하는 메서드를 재정의했을 때의 문제를 보여준다. (126쪽)
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();
    }
}

Super 클래스를 상속한 Sub 클래스를 작성한다.

상위 클래스의 생성자에서 재정의가 가능한 메서드를 호출하고 있다.

이렇게 생성자에서 재정의가 가능한 메서드를 호출하면 안된다.

상속을 받은 클래스는 상위 클래스의 생성자를 호출하게 된다.

    Sub() {
         super() // 생략 가능;
        instant = Instant.now();
    }

Sub 클래스를 생성할 때 상위 클래스인 Super 의 생성자를 호출하게 된다.

상위 클래스의 생성자에서 호출하는 메서드를 오버라이딩 했으므로

상위 클래스의 생성자에서 하위 클래스의 재정의한 메서드를 호출하게 된다.

같은 맥락으로 CloneableSerializable 을 구현할때는 조심해야한다.

CloneableSerializable 은 어떤 인스턴스를 만들어내는 특징이 있다.

Cloneable 은 현재 객체와 동일한 객체를 만들어내고 Serializable 은 역직렬화하며 바이트스트림을 객체로 복원하게된다.

생성자를 호출하는 것과 비슷한 효과가 있다.

CloneableSerializable 에서 메서드들에서 호출하는 메서드들을 재정의하면 안된다.


상속을 허용할 때 주의할 점과 문서화를 해야하는 일들이 많다.

상속을 허용할 것이라면 반듯이 위의 주의사항을 지켜주어야하고, 그렇지 않다면 상속을 금지 시켜야한다.

상속을 막는 방법은 final 클래스를 사용하는 것과

생성자를 모두 private 이나 package - private 으로 만드는 것이다.

생성자를 모두 package - private 으로 만들면 해당 패키지 내부에서 상속이 가능하다.

생성자를 모두 private 으로 만들면 해당 클래스 내부에서의 상속이 가능하다.

좀 더 클래스를 유연하게 사용할 것이라면 생성자를 모두 private 이나 package - private 만드는 방법도 좋은 방법이다.

반응형

댓글