본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Design Pattern/Java] 가급적 상속(Inheritance) 보다는 컴포지션(composition)을 사용하자.

by 돼지왕 왕돼지 2012. 3. 29.
반응형


안녕하세요 돼지왕왕돼지입니다.

오늘은 "가급적 상속(Inheritance)보다는 컴포지션(composition)을 사용하자" 라는 주제로 이야기하고자 합니다.

이 글은 "Effective Java" 의 내용을 요약 정리한 내용입니다.


상속의 안전함과 위험함.

안전함


- 동일한 프로그래머가 서브 클래스와 슈퍼 클래스의 구현을 관장하는, 같은 패키지 내에서 상속을 하는 것은 안전합니다. ( 아래 다뤄질 상속의 문제점들을 커버할 수 있기 때문입니다. )

- 클래스가 상속을 위해 특별히 설계되었으며, 문서화가 잘 된 클래스를 "확장(extends)"의 목적으로 상속하는 것도 안전합니다.


위험함.


- 서로 다른 패키지에 있으며, 확장을 목적으로 설계되지 않은 일반적인 클래스를 상속받는 것은 위험합니다.




무엇이 그렇게 위험한가?

- 상속은 캡슐화 ( encapsulation ) 을 위배합니다. 올바른 동작을 위해 서브 클래스는 자신의 수퍼 클래스가 구현하는 상세 내역에 의존하게 되는데요, 이 때 수퍼 클래스의 구현 내용은 소프트웨어 배포판이 바뀌면서 변경될 수 있습니다. 이렇게 되면 서브 클래스의 코드를 그냥 사용하게 되면 제대로 동작하지 않을 가능성이 높으며, 수퍼클래스의 변화를 항상 감지하고, 맞춰 진화해야 합니다.

- 차후 배포판에서 수퍼 클래스에 새로운 메소드가 추가되면 서브 클래스가 허약하게 되는 원인이 될 수 있습니다. 추후 배포판의 수퍼 클래스에 새 메소드를 추가할 때 그 메소드와 signature 는 동일하고, return type 이 다른 메소드의 함수가 서브 클래스에 이미 있다면, 잘못된 오버라이딩으로 서브 클래스에서 컴파일 에러가 생깁니다. 반대로, 서브 클래스의 새 함수와 같은 signature 와 return type 을 가진 함수를 수퍼클래스에서 정의한다면, 잘못된 오버라이딩을 한 셈이 됩니다.

- 클래스의 내부 구현을 쓸데 없이 노출시킬수도 있습니다. 그렇게 되면, 외부 API 와 내부 구현이 밀접하게 연계되어 클래스의 성능을 제한하게 되지요. 심각하게는 클라이언트가 내부 메소드나 필드를 직접 접근할 수도 있다는 것입니다. ( 잘못된 설계와 구현일수록 더욱 심각해집니다. ) 더 심각한 것은, 클라이언트가 수퍼 클래스를 직접 수정하여 서브 클래스의 불변성을 저해할 수 있습니다. 




그럼 방법은 무엇인가?

클래스를 상속하는 대신 기존 클래스(super class로 하려 했던)의 인스턴스를 참조하는 private 필드를 서브 클래스로 만들고자 했던 클래스에 만듭니다. 이런 패턴을 컴포지션(composition) 이라고 합니다. 그리고 새 클래스의 각 인스턴스 메소드에서는 기존 클래스에 포함된 함수들을 호출하여 결과를 반환해줍니다. 이것을 포워딩(forwarding) 이라고 합니다. 
이렇게 함으로서 새 클래스는 기존 클래스의 내부 구현에 종속되지 않으며, 새로운 메소드를 추가하더라고 새 클래스에는 영향을 주지 않습니다. 이런 클래스를 wrapper class 라고 합니다.




Composition & Forwarding 을 하는 Wrapper class 는 단점이 없는가?

- 객체 자신의 참조를 다른 객체에게 전달하는 콜백 프레임워크( callback framework ) 에서의 사용은 부적합합니다. 래퍼 객체에 포함된 객체는 자신의 래퍼 객체를 알지 못하므로, 콜백을 할 경우 자신의 참조만을 다른 객체에 전달하기 때문입니다.
 

- Forwarding method 들의 호출 또는 Wrapper instance 의 빈번한 메모리 할당과 해지가 성능에 영향을 주지는 않을까 싶지만, 실제로 큰 영향을 주지 않는다는 것으로 밝혀졌다고 합니다.




그럼 상속( Inheritance ) 는 언제 쓸 수 있는것인가?

- 두 클래스가 "is-a" 관계일 때 클래스 B를 클래스 A의 서브 클래스로 확장(상속)해야 합니다. 만일 클래스 B를 클래스 A의 서브 클래스로 만들고 싶다면 "모든 B 객체가 진정한 A인가?" 라는 질문을 던져봐야 합니다. 자신있게 "예" 라고 대답할 수 없다면 상속을 하지 말아야 합니다. 바로 Composition 을 고려해봐야 합니다. B에서 A의 instance 를 private 으로 포함하고, 작고 간단한 API 메소드를 포함하는 것이죠.

- 수퍼클래스의 API 에 결함은 없는지, 결함이 있다면 그 결함을 그대로 상속받을 것인지를 생각해보아야 합니다. 결함이 있고, 그 결함을 받아들일 수 없다면, Composition 을 사용하는 것이 좋습니다.  Composition 으로 그 결함을 감출 수 있기 때문이죠.



반응형

댓글