아이템 18 - 상속보다는 컴포지션을 사용하라
JAVA ·상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다.
상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서라면 안전하다.
확장할 목적으로 설계되었고 문서화도 잘 된 클래스도 마찬가지로 안전하다.
구현 상속의 문제점
- 일반적인 구체 클래스를 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다.
- 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다.
- 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
- 상위 클래스는 릴리즈마다 내부 구현이 달라질 수 있으며, 그 여파로 코드 한 줄 건드리지 않은 하위 클래스가 오동작 할 수 있다.
- 상위 클래스에서 새로운 메서드가 생긴다면? 코드가 깨질 여지가 많아진다.
- 재정의로 인한 문제, 메서드와 시그니처가 같고 반환 타입이 다르다면 컴파일 문제 등…
- 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다.
해결방안
- 컴포지션, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 해라.
- 새 클래스의 인스턴스 메서드들은 기존 클래스에 대응하는 메서드를 호출해 그 결과를 반환해라.
- 이 방식을 전달(forwarding)이라 하며, 새 클래스의 메서드들을 전달 메서드라고 부른다.
- 그 결과, 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗어나며, 심지어 기존 클래스에 새로운 메서드가 추가되더라도 전혀 영향받지 않는다.
컴포지션을 써야 할 상황에서 상속을 사용하는 건 내부 구현을 불필요하게 노출하는 꼴이다.
그 결과 API가 내부 구현에 묶이고 그 클래스의 성능도 영원히 제한된다.
더 심각한 문제는 클라이언트가 노출된 내부에 직접 접근할 수 있다는 점이다.
컴포지션 대신 상속을 사용하기로 결정하기 전에 확인해라.
1) 확장하려는 클래스의 API에 아무런 결함이 없는가?
2) 결함이 있다면, 이 결함이 새 클래스의 API까지 전파돼도 괜찮은가?
컴포지션으로는 이런 결함을 숨기는 새로운 API를 설계할 수 있지만,
상속은 상위 클래스의 API를 ‘그 결함까지도’ 그대로 승계한다.