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

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

by 돼지왕 왕돼지 2016. 10. 27.
반응형

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


API 결함, callback framework, composition, Decorate, Decorator pattern, Effective JAVA, encapsulation, extends, forwarding, Inheritance, is-a, isa relation, isa 관계, Overriding, Private, SubType, Wrapper, Wrapper Class, [Effective Java] 가급적 상속(inheritance) 보다는 컴포지션(composition)을 사용하자., 가장 좋은 도구, 각 인스턴스 메소드, 강력, 강력한 방법, 같은 패키지, 결과, 관장, 구현, 기존 메소드, 기존 클래스, 내, 다른 패키지, 단점, 덧붙여, 데코레이터 패턴, 동일, 래퍼 객체, 래퍼 클래스, 레퍼, 메소드, 문서화, 문제, 반환 타입 다른, 변경, 부실한 소프트웨어, 상세 내역 의존, 상속, 상속을 위해 설계, 새 메소드 추가, 새 클래스, 새로운 클래스, 서브 클래스, 설계, 소프트웨어 배포판, 수퍼 클래스, 슈퍼 클래스, 슈퍼 클래스 구현 내역, 슈퍼 클래스에 맞춰 진화, 시그너처, 시그니처, 안전, 오버라이딩, 위험, 의도하지 않은 오버라이딩, 인스턴스, 일반적인 실체 클래스 상속, 자신의 래퍼, 재사용, 진정한 a, 진정한 서브 타입, 참조, 초래, 치장, 캡슐화, 컴파일 에러, 컴포지션, 컴포지트, 코드, 콜백 프레임워크, 클래스, 튼튼, 패키지 내 상속, 포워딩, 프로그래머, 호출, 확장

-
상속은 코드를 재사용하는 강력한 방법이다.
그러나 일을 하는데 가장 좋은 도구는 아니다.
잘못 사용하면 부실한 소프트웨어를 초래한다.


-
동일 프로그래머가 서브 클래스와 수퍼 클래스의 구현을 관장하는 같은 패키지 내에서 상속을 사용하는 것은 안전하다
상속을 위해 특별히 설계되고 문서화된 클래스를 확장(extends) 하기 위해 상속을 사용하는 것도 안전하다.
그러나 다른 패키지에 걸쳐 일반적인 실체 클래스로부터 상속을 받는 것은 위험하다.


-
상속은 캡슐화(encapsulation) 을 위배한다.
올바른 동작을 위해 서브 클래스는 자신의 수퍼 클래스가 구현하는 상세 내역에 의존한다.
수퍼 클래스의 구현 내역은 소프트웨어 배포판이 바뀌면서 변경될 수 있다.
따라서 서브 클래스는 수퍼 클래스에 맞춰 진화해야 한다.


-
수퍼 클래스의 기존 메소드를 오버라이딩 하지 않고 새 메소드를 추가할 때는 안전하다고 생각할 수 있으나,
꼭 그렇지는 않다.
추후 배포판의 수퍼 클래스에서 새 메소드를 추가할 때 그 메소드와 시그너처는 동일하고 반환 타입이 다른 메소드가 서브 클래스에 이미 있다면 메소드 오버라이딩을 잘못한 것으로 간주하여 컴파일 에러가 생긴다.
슈퍼 클래스와 동일한 시그니처 및 반환 타입을 갖고 있다면, 의도하지 않은 오버라이딩을 한 셈이 되어 또 다른 문제가 발생한다.


-
클래스를 확장(상속)하는 대신 기존 클래스의 인스턴스를 참조하는 private 필드를 새로운 클래스에 두는 컴포지션(composition) 으로 상속의 문제점들을 해결할 수 있다.


-
새 클래스의 각 인스턴스 메소드에서는 포함된 기존 클래스 인스턴스의 대응되는 메소드를 호출하여 결과를 반환할 수 있다. ( 포워딩(forwarding) )
새 클래스는 기존 클래스의 내부 구현에 종속되지 않으며, 기존 클래스에 새로운 메소드를 추가하더라도 새 클래스에는 영향을 주지 않는다.


-
컴포지트를 이용하여 모든 public 함수들을 forwarding 한 클래스를 래퍼(wrapper) 클래스라고 하는데,
이것을 데코레이터 패턴(decorator pattern) 이라고도 한다. ( 기존 클래스에 덧붙여 치장(decorate)하기 때문 )
래퍼 클래스의 단점은 거의 없지만, 컴포지트 된 객체 자신의 참조를 다른 객체에게 전달하는 콜백 프레임워크(callback framework)에서의 사용은 부적합하다.
래퍼 객체에 포함된 객체는 자신의 래퍼 객체를 알지 못하기 때문이다.


-
만약 상속을 꼭 사용하고 싶다면, 서브 클래스가 수퍼 클래스의 진정한 서브타입(subtype)인 경우에만 상속을 사용하는 것이 좋다.
즉 두 클래스가 "is-a" 관계일 때만 상속해야 한다.
만일 클래스 B를 클래스 A의 서브 클래스로 만들고 싶다면 다음 질문을 생각하라.
"모든 B객체가 진정한 A 인가?"
자신있게 "yes" 를 하지 못한다면 컴포지션을 고려해야 한다.


-
컴포지션 대신 상속을 사용하기로 결정하기 전에 최종으로 생각해봐야 할 질문들이 있다.
상속을 하려고 생각하는 클래스가 API 결함이 없는가?
만일 결함이 있으면 그 결함을 서브 클래스의 API 에 그대로 상속받을 것인가?



Summary


상속은 강력하지만 캡슐화를 위배하므로 문제가 된다.
서브 클래스와 수퍼 클래스간에 진정한 서브 타입 관계가 있을 때만 적합하다.
서브 클래스가 수퍼 클래스와 다른 패키지에 있고, 수퍼 클래스가 상속을 위해 설계된 것이 아니라면 상속은 서브 클래스를 망칠 수 있다.
상속 대신 컴포지션과 포워딩을 사용하자.
래퍼 클래스를 구현하는데 적합한 인터페이스가 이미 존재한다면 더욱 더 컴포지션을 사용하자.
서브 클래스보다 래퍼 클래스가 더욱 튼튼하고 강력하다.





반응형

댓글