Skip to content

Latest commit

 

History

History
169 lines (130 loc) · 5.29 KB

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

File metadata and controls

169 lines (130 loc) · 5.29 KB

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

📩 상속을 고려한 설계와 문서화

문서화하지 않은 외부 클래스 상속은 위험⚠️!

  • 프로그래머의 통제권 밖
  • 언제 변경될 지 모름

상속을 고려한 설계와 문서화👍란?

  • 재정의 가능 메서드를 호출할 수 있는 '모든 상황' 문서화
    • API의 공개된 메서드에서 호출되는 재정의 가능 메서드 언급
    • 내부에서 어떻게 재정의 가능 메서드를 이용하는지 설명

재정의 가능 메서드 란?

  • final이 아님
  • public, protected, default 메서드
  • static이 아님
  • 즉 클래스 밖에서 접근 / 수정 가능한 모든 메서드

문서화 예시

public abstract class AbstractCollection<E> implements Collection<E> {
    /**
     * {@inheritDoc}
     *
     * @implSpec
     * 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
     * {@code UnsupportedOperationException} <<if the iterator returned by this
     * collection's iterator method does not implement the {@code remove}
     * method and this collection contains the specified object.>>
     *
     * @throws UnsupportedOperationException {@inheritDoc}
     * @throws ClassCastException            {@inheritDoc}
     * @throws NullPointerException          {@inheritDoc}
     */
    public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o == null) {
            while (it.hasNext()) {
                if (it.next() == null) {
                    it.remove();
                    return true;
                }
            }
        }
        //...
    }
}
  • @implSpec 태그를 붙여주면 javaDoc 도구가 절 생성
  • AbstractCollection.remove() 안에서 Iterator.remove()를 사용
  • 재정의 가능 메서드가 주는 영향 및 주의사항을 문서화하기
public interface Iterator<E> {
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    //...
}
  • Iterator.remove()default 접근제어자, 즉 재정의 가능 메서드 이다.

⛏ 훅(hook)

캠슐화를 해치는 상속

  • 상속하면 필연적으로 내부 구현방식을 설명해야 함
  • 캠슐화를 해침ㅠㅠ

훅을 선별하자

  1. 상속용 클래스 내부 동작 과정 중간에, 끼어들 수 있는 지점 hook를 만들자
  2. 해당 지점을 protected 메서드로 공개
  3. 드물게는 protected 필드로 공개

어떤 메서드를 으로 만들지?

  • 정답은 없다. 두뇌풀가동...!
  • 실제 하위클래스를 만들어 시험
  • 은 적어야 하지만, 너무 적으면 상속의 이점이 사라짐

🔍 상속용 클래스 검증

  • 배포 전 반드시 검증!!

어떻게 검증하지?

  • 실제 하위클래스를 만들어 시험
  • 안 쓰이는 protectedprivated로 변경
  • 검증을 위해 3개 정도의 하위클래스 생성하기
    • 하나 이상은 제 3자가 작성

추가제약

상속용 클래스 생성자가 재정의 가능 메서드를 호출해서는 안됨!!

public class Test {
    public Test() {
        overrideMe();
    }

    public void overrideMe() {
        System.out.println("Test method");
    }
}



public class SubTest extends Test{
    private final Date date;

    public SubTest() {
        date = new Date();
    }

    @Override
    public void overrideMe() {
        System.out.println(date);
    }

    public static void main(String[] args) {
        SubTest sub = new SubTest();
        sub.overrideMe();
    }
}
> null
//subTest main 실행
//원래는 data 값이 나오는 걸 의도했지만, null이 나옴
  1. 상위 클래스(Test)의 생성자가 하위 클래스(subTest)의 생성자보다 먼저 실행
  2. 상위 클래스(Test) 에서 재정의한 메서드가 하위 클래스(subTest) 의 생성자보다 먼저 호출된다.
  3. 프로그램 오작동

clone, readObject 메서드가 재정의 가능 메서드를 호출해서는 안됨!!

Serializable이 구현된 상속용 클래스

  • readResolvewriteReplace를 메서드로 가질 시
  • 해당 메서드들을 protected로 선언해야 함......

⚖️ 결론

  • 클래스를 상속용으로 설계하려면 엄청난 노력이 들며, 제약이 많다!!
  • 일반적인 구체 클래스(final도 아니고 상속용도 아닌... 우리가 주로 쓰는 클래스)도 상속가능해 위험하다!!

상속용으로 설계하지 않은 클래스 : 상속을 금지🚫 하자!

  • 방법 1 : 클래스를 final로 선언
  • 방법 2 : 모든 생성자를 package-private선언 -> public 정적 팩토리 메서드(아이템 17) 사용
    • 헌치 픽
  • 방법 3 : 인터페이스 사용
  • 방법 4 : 래퍼 클래스 패턴(아이템 18) 사용

구체 클래스는요?😅

  • 표준 인터페이스 없이 상속을 금지하면 사용하기 불편해진다...
  • 방법 1 : 클래스 내부에서 재정의 가능 메서드를 사용하지 않고, 이를 문서화
  • 방법 2 : 래퍼 클래스 패턴(아이템 18) 사용