객체지향 설계의 핵심 원칙 중 하나는 "상속보다는 포함을 사용하라"는 것이다. 객체지향 프로그래밍을 수행할 때 클래스 간의 관계 설정을 두고 고심하는 경우가 많다. 상속과 포함은 대표적인 클래스 관계 설정 방식으로서, 일반적으로는 포함 관계를 사용하는 것이 더 나은 설계로 귀결되는 경우가 많다. 그 이유를 자세히 분석해 보자.
상속, 강력하나 신중해야 할 관계
상속은 "IS-A" 관계를 모델링하는 데 유용하다. 예를 들어, "개는 동물이다(Dog IS-A Animal)"와 같이 명확한 상위-하위 관계를 표현할 때 상속을 사용하면 부모 클래스의 속성과 기능을 자식 클래스가 그대로 물려받아 코드 재사용성을 높일 수 있다.
그러나 상속은 다음과 같은 잠재적인 문제점을 내포한다.
- 취약한 기반 클래스 문제 (Fragile Base Class Problem): 부모 클래스의 변경이 자식 클래스에 예상치 못한 영향을 미칠 수 있다. 이는 유지보수를 어렵게 하고 시스템을 불안정하게 만들 수 있다.
- 불필요한 인터페이스 상속: 자식 클래스가 부모 클래스의 모든 메서드를 실제로 사용하지 않더라도 상속받게 되어 인터페이스가 불필요하게 커질 수 있다. 이는 클래스를 이해하고 사용하기 어렵게 만든다.
- 엄격한 결합도 (Tight Coupling): 상속은 부모 클래스와 자식 클래스 간의 강한 결합을 형성한다. 부모 클래스의 구현 방식에 자식 클래스가 의존하게 되어 유연성이 저하되고 변화에 취약해진다.
- 클래스 계층의 복잡성 증가: 깊고 복잡한 상속 계층은 코드를 이해하고 유지보수하기 어렵게 만들 수 있다. 특히 다중 상속의 경우, 복잡성은 더욱 가중된다.
- 실행 중 행위 변경의 어려움: 상속은 컴파일 시점에 클래스 간의 관계가 결정되므로, 객체의 행위를 실행 중에 유연하게 변경하기 어렵다.
포함, 유연하고 강력한 대안
포함(Composition)은 한 클래스가 다른 클래스의 인스턴스를 필드로 가지고, 그 객체의 기능을 활용하는 방식이다. 이는 "HAS-A" 관계를 모델링하는 데 적합하다. 예를 들어, "자동차는 엔진을 가지고 있다(Car HAS-A Engine)"와 같이 객체가 다른 객체를 "가지고" 사용하는 관계를 표현할 때 포함을 사용한다.
포함 관계는 상속에 비해 다음과 같은 장점을 제공한다.
- 높은 유연성 (High Flexibility): 포함된 객체를 필요에 따라 쉽게 교체하거나 변경할 수 있다. 객체 간의 결합도가 낮으므로, 한 클래스의 변경이 다른 클래스에 미치는 영향이 적다.
- 향상된 유지보수성 (Improved Maintainability): 낮은 결합도는 코드의 유지보수성을 크게 향상시킨다. 각 클래스가 독립적으로 변경될 수 있어 시스템 전체의 안정성을 높인다.
- 코드 재사용 증대 (Increased Code Reusability): 필요한 기능만을 가진 객체를 포함하여 사용할 수 있다. 상속처럼 불필요한 기능을 물려받을 필요가 없다.
- 실행 중 행위 변경 용이 (Easy Behavior Change at Runtime): 포함된 객체를 동적으로 변경함으로써 객체의 행위를 실행 중에 유연하게 변경할 수 있다. 이는 시스템의 적응성을 높인다.
- 상속의 문제점 회피: 앞서 언급한 상속의 잠재적인 문제점들을 자연스럽게 피할 수 있다.
"상속보다는 구성 (Composition over Inheritance)" 원칙
이러한 이유로 객체지향 설계에서는 "상속보다는 구성 (Composition over Inheritance)" 원칙이 강조된다. 이는 클래스 간의 관계를 설정할 때 상속보다는 포함 관계를 우선적으로 고려해야 함을 의미한다.
물론 상속이 모든 상황에서 부적절한 것은 아니다. 명확한 IS-A 관계가 존재하고, 코드 재사용성이 매우 높으며, 클래스 계층이 단순할 경우에는 상속이 효과적인 선택이 될 수 있다. 하지만 이러한 경우에도 상속을 신중하게 사용해야 하며, 포함 관계를 통해 더 나은 설계를 할 수 있는지 항상 고려해야 한다.
결론
객체지향 설계를 수행할 때 클래스 간의 관계를 어떻게 설정할지는 시스템의 유연성, 유지보수성, 확장성에 지대한 영향을 미친다. 일반적으로 상속보다는 포함 관계를 사용하는 것이 더 나은 설계를 위한 길이며, "상속보다는 구성" 원칙을 명심하고 실제 개발에 적용해 볼 것을 권장한다.