일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- C
- 안드로이드 구글맵
- Python
- Kotlin
- kodility
- C/C++
- 코틀린
- Django REST Android
- UWP
- flutter firestore
- Flutter TextField
- Java
- Django REST framework
- mfc
- 알고리즘
- NDK
- FLUTTER
- Android
- android architecture component
- livedata
- android push
- Django REST
- RxAndroid
- Android P
- Rxjava2
- dart
- RxJava
- C++
- 안드로이드
- 프로그래머스
- Today
- Total
개발하는 두더지
[Effective Java 규칙17] 상속을 위한 설계와 문서가 없다면 상속하지 마라 본문
[Effective Java 규칙17] 상속을 위한 설계와 문서가 없다면 상속하지 마라
Effective Java 2/E 책과 구글링을 통해 내용을 정리하고 개인적인 견해가 포함된 글입니다.
상속하고 메서드를 재정의하면 무슨일이 생기는지 어떻게 사용하는지 반드시 문서에 남겨야 한다. 하지만 문서만 제대로 작성했다고 상속에 적합한 설계가 되지는 않는다. 클래스 내부 동작을 자유자재로 수정/접근할 수 있는 메서드를 신중하게 선정한 다음에 protected 메서드 형태로 제공해야 한다. 이 protected로 사용할 메서드를 어떻게 정할 것인가? 정확한 답은 없다. 열심히 생각하고 신중하게 고른 다음, 실제로 하위 클래스들을 만들어서 테스트하는 것이 최선이다. 저자에 의하면 상속을 위해 하위 클래스 3개정도 만들어 테스트하는 것이 적절하다고 한다.
널리 사용될 클래스를 상속에 맞게 설계할 때는 문서에 명시한 사용방법뿐 아니라 메서드와 필드를 protected로 선언한 내용들을 절대 고쳐서는 안되며 항상 유지해야 한다.
그리고 상속을 위해 지켜야할 제약사항들이 더 있는데
1. 생성자는 재정의 가능 메서드를 호출하면 안된다.
상위 클래스 생성자에서 재정의 가능 메서드를 호출하고 하위 클래스에서 상위 생성자를 상속받은 다음 하위 클래스 객체를 만들고 재정의 가능 메서드를 호출하면 어떻게 될까? 상위 클래스 생성자에서 재정의 가능 메서드를 호출하면 아직 초기화되지 않은 하위 클래스의 재정의 가능 메서드가 호출된다. 만약 재정의 가능 메서드가 멤버 필드를 호출하고있다면 NullPointException 예외가 발생한다.
2, 3
Cloneable과 Serializable 인터페이스를 사용할때는 상속 클래스를 설계하기가 더 어렵다. 일반적으로 상속할때 이런 인터페이스를 구현하는 것은 바람직 하지 않다. 클래스를 상속받을 클래스/프로그래머에게 너무 많은 일을 떠넘기기 때문이다. 하지만 인터페이스를 선택적으로 구현할 수 있도록 하는 방법이 있다. clone이나 readObject 메서드안에서 재정의 가능 메서드를 호출하지 말아야 한다. readObject안에서 재정의 가능 메서드를 호출하면 하위 클래스 객체가 역직렬화(deserialize)되기전에 재정의 가능 메서드가 호출되기 때문에 1번과 같은 문제가 발생할 가능성이 높다. Cloneable과 Serializable을 구현하는 코드를 작성해본 적이 없지만 1번과 같은 문제점이 발생할 수 있다는 점에서 기억해둘만 한 내용인 것 같다.
상속을 위해 클래스를 설계하면 클래스에 상당한 제약이 생긴다는건 위의 내용을 보면 알 수 있다. 하지만 인터페이스에 대한 스켈레톤 구현, 추상 클래스를 만드는 경우라면 올바른 경우이다. 그 외에는 되도록이면 상속에 맞게 설계하고 문서화되지 않은 클래스는 상속하지 않는 것이 좋다.
어떻게 상속을 막는가?
클래스를 final로 선언하여 상속을 막아버리던가
모든 생성자를 private과 package-private으로 선언하고 public 정적 팩터리 메서드를 추가하던가(규칙1)
인터페이스를 구현하는 래퍼 클래스를 쓰거나(규칙16)
이런 방법을 사용해야한다.
평소에 클래스 상속을 쓸만큼 클래스 설계를 많이 해보지 않았고, 되로록 인터페이스를 만들어서 구현하는 방법을 사용했음.
이번 규칙을 조금 더 자세하게 설명하기 위해 어떤 샘플을 만들어볼지 고민해보자.
'Java,Android' 카테고리의 다른 글
WorkManager로 안드로이드 하위 버전부터 오레오 버전까지 백그라운드 작업 통합 (1) | 2018.09.19 |
---|---|
[Effective Java 규칙18] 추상 클래스 대신 인터페이스를 사용하라 (0) | 2018.09.17 |
[Effective Java 규칙16] 상속(extends)하는 대신 구현(implements)하라 (0) | 2018.09.17 |
[Effective Java 규칙15] 변경 가능성을 최소하하라 (0) | 2018.09.14 |
[Effective Java 규칙13] 클래스와 멤버의 접근 권한은 최소화하라 (0) | 2018.09.14 |