Item 17. 계승을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 계승을 금지하라

상속을 위한 설계와 문서를 갖추기 위해서는,,,

문서화

override가능한 메서드의 경우, 메서드 주석 마지막에 override에 관한 정보(메서드 내부 동작 원리)를 남겨라

  • 메서드를 어떤 순서로 호출하는지
  • 호출 결과가 어떤 영향을 미치는지
  • ex. java.util.AbstractCollection

      public abstract class AbstractCollection<E> implements Collection<E> {
          public abstract Iterator<E> iterator();
    
          /**
           * {@inheritDoc}
           *
           * <p>This implementation iterates over the collection looking for the
           * specified element.  If it finds the element, it removes the element
           * from the collection using the iterator's remove method.
           *
           * <p>Note that this implementation throws an
           * <tt>UnsupportedOperationException</tt> if the iterator returned by this
           * collection's iterator method does not implement the <tt>remove</tt>
           * method and this collection contains the specified object.
           *
           * @throws UnsupportedOperationException {@inheritDoc}
           * @throws ClassCastException            {@inheritDoc}
           * @throws NullPointerException          {@inheritDoc}
           */
          public boolean remove(Object o) {
              ...
          }
      }
    
    • using the iterator's remove method
    • collection's iterator method does not implement the remove method and this collection contains the specified object
    • => iterator method를 재정의 하면 remove가 영향을 받는다

cf. 상속은 캡슐화의 원칙을 침해한다


상속을 위한 설계

내부 동작에 개입할 훅 제공

클래스 내부 동작에 개입할 수 있는 훅을 protected method의 형태로 제공하라

  • ex. java.util.AbstractList
      public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
           /**
           * 어쩌구저쩌구 
           * <p>This method is called by the {@code clear} operation on this list
           * and its subLists.  Overriding this method to take advantage of
           * the internals of the list implementation can <i>substantially</i>
           * improve the performance of the {@code clear} operation on this list
           * and its subLists.
           *
           * <p>This implementation gets a list iterator positioned before
           * {@code fromIndex}, and repeatedly calls {@code ListIterator.next}
           * followed by {@code ListIterator.remove} until the entire range has
           * been removed.  <b>Note: if {@code ListIterator.remove} requires linear
           * time, this implementation requires quadratic time.</b>
           *
           * @param fromIndex index of first element to be removed
           * @param toIndex index after last element to be removed
           */
          protected void removeRange(int fromIndex, int toIndex) {
              ...
          }    
      }
    
    • called by the {@code clear} operation on this list and its subLists
    • 얘를 구현 하면 -> improve the performance of the {@code clear} operation
    • 만약 없었다면? clear의 성능이 O(n^2)임을 감안하고 사용하거나, subList자체를 다시 짜야함

override 가능한 메서드는 내부 호출 금지

생성자의 경우

생성자는 override 가능한 메서드를 호출하면 안됨

  • 인스턴스 생성시 호출 순서 : 상위 클래스 생성자 -> 하위 클래스 생성자
    • 그래서 명시적으로 super(); 를 하기도 하는군...
  • override한 메서드가 하위 클래스 생성자 내 로직에 의존이 있을 경우, 에러 발생
  • ex. test

      public class Super {
          public Super() {
              overrideMe();
          }
          public void overrideMe() {}
      }
    
      public final class Sub extends Super {
          private final Date date;
    
          Sub() {
              // super();
              date = new Date();
          }
    
          @Override
          public void overrideMe() {
              System.out.println(date);
          }
    
          public static void main(String[] args) {
              Sub sub = new Sub();
              sub.overrideMe();
              // null
              // Fri Sep 08 10:26:05 KST 2017
          }
      }
    
    • Super() -> overrideMe 호출
    • Sub.overrideMe 실행
    • Sub() -> date set
    • sub.overrideMe() 호출

Cloneable/Serializable 인터페이스 사용은 최대한 막자

  • 상속받은 하위 클래스에 너무 큰 책임이 몰림
  • 굳이 쓴다면, 생성자와 비슷하게 동작하는 clone/readObject 메서드는 override가능한 메서드를 호출하지 않도록
  • 굳이 쓰고, readResolve/writeReplace 메서드가 있다면, protected로 선언해야함 => private일 경우 하위 클래스에서 해당 메서드를 무시 (???)

테스트

  • protected 멤버는 최대한 적어야 함 -> 구현 세부 사항에 대한 서약이 되기 때문
  • 상속 가능한 클래스가 잘 설계되었는지 테스트하려면, 직접 하위 클래스를 만들어 봐라
    • 이 사람 경험으로는, 하위 클래스 3개면 충분. 3개중 하나 이상은 상위 클래스 구현자가 아닌 사람인게 좋음

하위 클래스 생성을 금지하는 법

  • class를 final로 선언
  • 모든 생성자를 private/default로 선언 후, 생성자 대신 정적 팩터리 메서드 사용

results matching ""

    No results matching ""