brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Mar 26. 2018

클래스와 인터페이스

#16 가급적 상속보다는 컴포지션을 사용하자

Effective Java - 클래스와 인터페이스


#16 가급적 상속(inheritance)보다는 컴포지션(composition)을 사용하자


상속은 재사용성 측면에서 여러 장점이 있지만, 잘못 이용하면 문제점들이 발생합니다.


안전한 상속

동일한 프로그래머가 같은 패키지 내에서 상속

상속을 위해 특별히 설계되고 문서화된 클래스를 상속


위험한 상속

상이한 패키지에 걸쳐 일반적인 실체 클래스로부터 상속


상속과 컴포지션에 대해 알아보겠습니다.




캡슐화 위배


#01 캡슐화 위배


기본적으로 서브 클래스는 슈퍼 클래스가 구현하는 상세 내역을 의존하게 됩니다.

따라서 슈퍼 클래스가 변경되면 서브 클래스가 제대로 동작하지 않을 수 있습니다.

이를 방지하기 위해 슈퍼 클래스가 변경되면 서브 클래스도 이에 맞게 수정해야 합니다.


아래는 HashSet을 상속받아 요소들을 추가했던 횟수를 알아내는 클래스입니다.

add와 addAll 메서드는 슈퍼 클래스의 메서드들을 오버 라이딩했습니다.

#02 InstrumentedHashSet class


addAll 메서드를 통해 3개의 String 요소들을 추가하면, addCount가 3이 반환될 거 같지만 실제로는 6이 반환됩니다.

#03 getAddCount


슈퍼 클래스의 addAll 메서드의 내부 코드를 보면, add 메서드를 호출하고 있습니다.

따라서 addAll 메서드에서 3이 더해진 후, add 메서드로 인해 다시 3이 더해져 6을 반환합니다.

#04 addCount logic


이 문제를 해결하기 위해 addAll 메서드를 오버 라이딩하지 않고, 별도의 메서드를 구현할 수도 있습니다.

하지만 이 또한 슈퍼 클래스의 변경으로부터 자유로울 수 없습니다.


이처럼 서브 클래스에서는 슈퍼 클래스에 의존하게 되고, 슈퍼 클래스가 변경 없이 계속 유지된다는 보장이 없기 때문에 의도하지 않게 서브 클래스에 문제가 발생할 수 있습니다.




컴포지션(composition)


#05 컴포지션


상속으로 인한 문제를 해결하기 위해 컴포지션을 이용할 수 있습니다.

컴포지션이란, 기존 클래스(슈퍼 클래스)의 인스턴스를 참조하는 private 필드를 새로운 클래스(서브 클래스)에 주는 설계 방식을 말합니다. (기존 클래스가 새 클래스의 컴포넌트로 포함)


아래는 HashSet 인스턴스를 참조하는 private 필드를 추가한 컴포지션으로 구현한 클래스입니다.

addAll 메서드는 HashSet 인스턴스의 addAll 메서드를 호출하여 반환하는 역할을 합니다.

이를 포워딩(forwarding)이라 하고, 이 메서드를 포워딩 메서드라고 합니다.

#06 Forwarding InstrumentedHashSet class


이렇게 하면, 새 클래스는 기존 클래스의 내부 구현에 종속되지 않고 원하는 결과를 얻을 수 있습니다.

#07 getAddCount




상속? 컴포지션?


그럼 언제 상속을 쓰고 언제 컴포지션을 써야 하는지 생각해봐야 합니다.

아래에 제시된 조건들을 모두 만족한다면, 안전한 상속을 구현할 수 있습니다.

서브 클래스가 슈퍼 클래스의 진정한 서브타입인 경우 ("is-a" 관계)

모든 서브 클래스 객체가 진정한 슈퍼 클래스 객체인가? (Yes)

같은 패키지 내에 있고, 슈퍼 클래스가 상속을 위해 설계되었는가? (Yes)

슈퍼 클래스 API에 결함이 있는가? (있다면 서브 클래스에도 영향을 줌)


만약 만족하지 못한다면 컴포지션을 쓰는 게 더 좋습니다.




매거진의 이전글 클래스와 인터페이스
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari