'상속'보다는 '컴포지션'을 사용해라. (예외 존재)

기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하자. 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻에서 이러한 설계를 컴포지션(composition, 구성)이라고 한다. 새 클래스의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출해 그 결과를 반환한다. 이 방식을 ‘전달’이라 하며, 새 클래스의 메서드들을 ‘전달 메서드’라 부른다. 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗어나며, 심지어 기존 클래스에 새로운 메서드가 추가되더라도 전혀 영향받지 않는다.

전제

지금부터 설명하는 내용은, 클래스가 인터페이스를 구현하거나 인터페이스가 다른 인터페이스를 확장하는 ‘인터페이스 상속’에는 적용되지 않는 내용이다.

상속보다는 컴포지션을 사용해야 하는 이유

  • 상속을 사용하게 되면 내부 구현을 불필요하게 노출하게 된다. 이로 인해 API가 내부 구현에 묶이고 그 클래스의 성능도 영원히 제한된다.
  • 상속은 캡슐화를 깨뜨린다. 즉, 클라이언트가 노출된 내부에 직접 접근할 수 있게된다. 이로 인해 API를 사용하는 클라이언트를 혼란스럽게 할 수 있다.
  • 상속은 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 상위 클래스는 릴리스마다 내부 구현이 달라질 수 있으며, 그 여파로 코드 한 줄 건드리지 않은 하위 클래스가 오동작할 수 있다는 말이다.

상속을 사용해도 되는 경우

밑의 조건을 모두 만족하는 경우에만 사용 가능하다.

1. 클래스 B가 클래스 A와 is-a 관계일 때만 클래스 A를 상속해야 한다.

2. 하위 클래스의 패키지와 상위 클래스의 패키지가 동일해야 한다.

3. 상위 클래스의 API에 아무런 결함이 없어야 한다.

4. 상위 클래스에 결함이 생겨서 하위 클래스의 API까지 결함이 전파되는 것을 감당할 수 있어야 한다.

상속은 상위 클래스의 API를 ‘그 결함까지도’ 그대로 승계하는 경우가 많기 때문이다.

References