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

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

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

상속용 클래스가 지켜야할 제약

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

    상속을 허용한 클래스에 있는 메서드에는 구현에 대한 정보를 문서화해야 한다 예를 들어 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다.

    재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 한다.

    @implSpec : 메서드의 내부 동작을 설명하는 “Implementaion Requirements”로 시작하는 주석을 만들어준다. (자바 8 이상)

    아래는 java.util.AbstractCollection에서 발췌한 예이다.

    public boolean remove(Object o)

    주어진 원소가 이 컬렉션 안에 있다면 그 인스턴스를 하나 제거한다(선택적 동작). 더 정확하게 말하면 이 컬렉션 안에 ‘Object.equals(o, e)가 참인 원소’ e가 하나 이상 있다면 그 중 하나를 제거한다. 주어진 원소가 컬렉션 안에 있었다면(즉, 호출 결과 이 컬렉션이 변경됐다면) true를 반환한다.

    Implementation Requirements: 이 메서드는 컬렉션을 순회하며 주어진 원소를 찾도록 구현되었다. 주어진 원소를 찾으면 반복자의 remove 메서드를 사용해 컬렉션에서 제거한다. 이 컬렉션이 주어진 객체를 갖고 있으나, 이 컬렉션의 iterator 메서드가 반환한 반복자가 remove 메서드를 구현하지 않았다면 UnsupportedOperationExceptioin을 던지니 주의하자.

    iterator 메서드를 재정의하면 remove 메서드의 동작에 영향을 줌을 확실히 알 수 있다.

  2. 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드로 공개해야 할 수도 있다.

    상속용 클래스에서 어떤 메서드를 protected로 노출해야 할지는 심사숙고하여 잘 예측해본 다음, 실제 하위 클래스를 만들어 시험해보는 것이 최선이다.

    protected 메서드는 내부 구현에 해당하므로 그 수는 가능한 적어야한다.

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

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

    이때 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존한다면 의도대로 동작하지 않을 것이다.

  4. Cloneable과 Serializable 인터페이스를 상속받은 클래스의 상속은 더 주의하자

    둘 중 하나라도 구현한 클래스는 상속할 수 있게 설계하는 것은 일반적으로 좋지 않은 생각이다.

    clone과 readObject는 생성자와 비슷한 효과를 내므로 상속용 클래스에서 Cloneable이나 Serializable을 구현할 지 정해야 한다면, 구현할 때 제약도 생성자와 비슷하다는 점에 주의하자

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

    clone이 완벽하지 않다면 복제본 내부 어딘가에서 여전히 원본 객체의 데이터를 참조하고 있다면 원본 객체도 피해를 입을 수 있다.

    Serializable을 구현한 상속용 클래스가 readResolve나 writeReplace 메서드를 갖는다면 private이 아닌 protected로 선언해야 한다. (private으로 선언하면 하위 클래스에서 무시되기 때문)

  5. 일반적인 구체 클래스의 경우 상속용으로 설계하지 않은 클래스는 상속을 금지하자

    상속을 금지하는 방법

    1. final을 선언하는 방법
    2. 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어주는 방법