상속을 고려한 설계와 문서화란?
우선, 메서드를 재정의하면 어떤 일이 일어나는지를 정확히 정리하여 문서로 남겨야 한다.
달리 말하면, 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있다.
그런데 마침 호출되는 메서드가 재정의 가능 메서드라면 그 사실을 호출하는 메서드의 API 설명에 적시해야 한다.
덧붙여서 어떤 순서로 담아야 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다.
더 넓게 말하면, 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야 한다.
예를 들어 백그라운드 스레드나 정적 초기화 과정에서도 호출이 일어날 수 있다.
API 문서의 메서드 설명 끝에서 종종 " Implementation Requirements"로 시작하는 절을 볼 수 있는데,
그 메서드의 내부 동작 방식을 설명하는 곳이다.
@implSpec 태그를 붙여주면 자바독 도구가 생성해준다.
AbstractCollection의 remove의 설명에 따르면 iterator 메서드를 재정의하면 remove 메서드의 동작에 영향을 줌을 알 수 있다.
iterator 메서드로 얻은 반복자의 동작이 remove 메서드의 동작에 주는 영향도 정확히 설명했다.
아이템 18에서 hashSet을 상속하여 add를 재정의한 것이 addAll까지 영향을 준다는 사실을 알 수 없었던 것과 대조적이다.
클래스를 안전하게 상속할 수 있도록 하려면 내부 구현 방식을 설명해야만 한다.
이는 상속이 캡슐화를 해치기 때문에 일어나는 안타까운 현상이다.
효율적인 하위 클래스를 큰 어려움 없이 만들 수 있게 하려면 클래스의 내부 동작 과정 중간에 끼어들 수 있는 hook을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.
상속용 클래스를 시험하는 방법은 직접 하위 클래스를 만들어보는 것이 유일하다.
상속용으로 설계한 클래스는 배포 전에 반드시 하위클래스를 만들어 검증해야한다.
상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.
책에서는 상속의 위험성을 여러가지 예제를 통해 경고해주는데,
그 중 Cloneble과 Serializable 인터페이스를 상속용 설계의 어려움을 한층 더해준다고 한다.
둘 중 하나라도 구현한 클래스를 상속할 수 있게 설계하는 것은 일반적으로 좋지 않은 생각이다.
클래스를 확장하려면 프로그래머에게 엄청난 부담을 지우기 때문이다.
나도 프로젝트를 수행하며 Serializable을 많이 사용해봤지만 이것으로 상속용 설계를 해본 것은 생각이 나지 않는다.
이런 문제를 해결하는 가장 좋은 방법은 상속용으로 설계하지 않은 클래스는 상속을 금지하는 것이다.
상속을 금지하는 방법은 두 가지다.
final로 클래스를 선언하거나, 모든 생성자를 private로 선언하고 public 정적 팩터리를 만들어주는 방법이다.
이 방법들은 다소 논란의 여지가 있지만 Set,List,Map같은 상속을 금지해도 개발하는데 어려움이 없는 좋은 예가 있다.
하지만 표준 인터페이스를 잘 구현해놓아야 한다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
21. 인터페이스는 구현하는 쪽을 고려해 설계해라( default 메서드) (0) | 2023.07.06 |
---|---|
20. 추상 클래스보다는 인터페이스를 우선하라(템플릿 메서드 패턴) (0) | 2023.07.03 |
18. 상속보다는 컴포지션을 사용하라 (0) | 2023.07.01 |
17. 변경 가능성을 최소화하라 (0) | 2023.07.01 |
16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.07.01 |