Search
Duplicate

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

작성자
챕터
4장 - 클래스와 인터페이스
최종 편집
2023/07/16 06:46
생성 시각
2023/07/16 02:36

안전한 상속방법

(인터페이스 “구현” 이 아닌 클래스 상속(extend) )
메서드를 재정의하면 어떤 일이 일어나는지를 정확히 정리하여 문서화 하라
상속용 클래스는 재정의 할 수 있는 메서드들을 내부적으로 어떻게(자기사용) 되는지 문서로 남겨야 한다.
클래스의 API 로 공개된 클래스에서 자신의 또 다른 메서드를 호출할 수도 있다.
자신의 또 다른 메서드가 재정의 가능 메서드라면 그 사실을 호출하는 메서드의 API 설명에 적시해야한다.
어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지
public, protected 중 final 이 아닌 모든 메서드
@implSpec 태그로 설정 가능
Implementation Requirements : 그 메서드의 내부 동작 방식을 설명하는 곳이다.

@ImplSpec 문서화

문서화 하는 방법

자바 제공 클래스 예시

아이템 18에서 봤던 addAll
remove
一tag "impl5pec:a:Implementation Requlrements:"
Java
복사
명령줄 매개변수로 지정해주면 @implSepc 태그를 사용할 수 있다.
좋은 API 문서란 어떻게? 가아니라 무엇 을 하는지 설명해야한다
상속이 캡슐화를 해치기 때문에 어쩔 수 없이 쓰는것.
안전하게 상속할 수 있다면 기술하지 말아야한다.

protected 로 공개해야 하는 함수

클래스의 내부 동작 과정 중간에 끼어들 수 있는 hook 을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.
상속용 클래스를 설계할 때 어떤 메서드를 protected로 할까?
실제 하위 클래스를 3개정도 만들어보고 시험해봐야한다
필요한 protected 멤버를 놓쳤다면, 하위 클래스를 작성할 때 티난다
하위 클래스를 여러개 만들때까지 전혀 protected 멤버를 안썼으면 private 여야할 가능성이 크다
⇒ 배포 전 반드시 하위 클래스를 만들어 검증해라

생성자 에서 주의해야 할점

상속용 클래스의 생성자는 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.
clone, readObject 도 생성자와 비슷한 역할을 하기 때문에 마찬가지이다.

생성자 재정의 에러 예제코드

class Super { public Super() { overrideMe(); } public void overrideMe() { } } final class Sub extends Super { private final String name; Sub(){ name = "ANONYMOUS"; } @Override public void overrideMe() { System.out.println("name = " + name); } } public class OverrideExtend { public static void main(String[] args) { Sub sub = new Sub(); Super subSuper = new Sub(); sub.overrideMe(); } } // 예상결과 // 출력없음 (Super.overrideMe()) // ANONYMOUS // null (Sub.overrideMe()) // ANONYMOUS @Getter public class SuperClass { private final String name; public SuperClass(String name) { this.name = name; this.introduce(); } public void introduce() { System.out.println("안녕하세요. 저는 " + name + "입니다."); } } /*-----------------------------------------------------------------------------*/ public class InferClass extends SuperClass { private final Integer age; public InferClass(String name, Integer age) { super(name); this.age = age; } @Override public void introduce() { System.out.println("안녕하세요. 저는 " + getName() + "인데요, 제 나이는 " + age + "입니다."); } } /*-----------------------------------------------------------------------------*/ @Test void 오버라이드_테스트() { InferClass inferClass = new InferClass("삼천갑자 동방삭", 180000); // 예상 : 안녕하세요. 저는 삼천갑자 동방삭인데요, 제 나이는 180000입니다. // 실제 : 안녕하세요. 저는 삼천갑자 동방삭인데요, 제 나이는 null입니다. }
Java
복사

일반 클래스의 안전한 상속

재정의 가능 메서드의 본문 코드를 private 도우미 메서드로 옮기고 이 도우미 메서드를 통해 호출
class AddSuper { public int addAll(int... numList) { int result = 0; for (int num : numList) { result += myAdd(result, num); } return result; } public int add(int a, int b) { return a + b; } private int myAdd(int a, int b) { return a + b; } } class Jasik extends AddSuper { @Override public int add(int a, int b) { return a * b; } } public class MakePrivate { public static void main(String[] args) { Jasik jasik = new Jasik(); int i = jasik.addAll(1, 2, 3, 4, 5); System.out.println("i = " + i); } } //result 0 // addMy result == 15
Java
복사

상속 금지시키기

1.
final class
2.
static factory 메소드 사용
class SomeClass{ private SomeClass(){} public static SomeClass valueOf(){ return new SomeClass(); } }
Java
복사