
자바 프로그래밍을 배우기 시작하면 가장 먼저 접하는 객체지향의 꽃은 단연 상속(Inheritance)입니다. 하지만 실무 프로젝트의 규모가 커질수록 많은 시니어 개발자들은 "상속을 피하고 컴포지션(Composition, 조합)을 사용하라"고 입을 모아 말합니다. 왜 그럴까요? 단순히 코드를 재사용하는 것을 넘어, 유지보수가 쉬운 견고한 시스템을 구축하기 위한 핵심 차이점을 심층 분석해 드립니다.
--- ## 1. 상속(Inheritance): "is-a" 관계의 강력한 결속
상속은 부모 클래스의 특성을 자식 클래스가 그대로 물려받는 방식입니다. 이는 코드 재사용 측면에서 매우 강력하지만, 부모와 자식 간의 결합도(Coupling)가 극도로 높아진다는 치명적인 단점이 있습니다.
상속의 특징과 위험성
- 화이트박스 재사용: 부모 클래스의 내부 구현이 자식 클래스에게 노출됩니다.
- 캡슐화 위반: 부모 클래스의 변경이 이를 상속받는 모든 자식 클래스에 예기치 못한 영향을 미칩니다.
- 유연성 부족: 컴파일 시점에 관계가 결정되므로 실행 중에 부모 클래스를 바꿀 수 없습니다.
--- ## 2. 컴포지션(Composition): "has-a" 관계의 유연함
컴포지션은 기존 클래스를 상속하는 대신, 새로운 클래스의 인스턴스 변수로 기존 클래스를 참조하는 방식입니다. 이를 통해 필요한 기능을 외부에서 주입받거나 내부에서 생성하여 사용합니다.
컴포지션의 특징과 장점
- 블랙박스 재사용: 객체의 내부 구현을 몰라도 인터페이스를 통해 기능을 사용할 수 있습니다.
- 낮은 결합도: 각 객체는 독립적으로 변경될 수 있어 유지보수가 용이합니다.
- 런타임 유연성: 실행 중에 인터페이스 구현체를 변경함으로써 동작을 다르게 설정할 수 있습니다.
--- ## 3. 상속 vs 컴포지션 핵심 비교
두 개념의 차이를 명확하게 이해하기 위해 주요 항목별로 비교해 보겠습니다.
| 비교 항목 | 상속 (Inheritance) | 컴포지션 (Composition) |
|---|---|---|
| 관계 유형 | is-a (~은 ~이다) | has-a (~은 ~을 가진다) |
| 결합도 | 강한 결합 (Tight Coupling) | 약한 결합 (Loose Coupling) |
| 캡슐화 | 파괴됨 (내부 구조 노출) | 보존됨 (인터페이스 소통) |
| 설계 변경 | 어려움 (계층 구조 전체 영향) | 쉬움 (독립적 부품 교체) |
| 다형성 구현 | 오버라이딩 위주 | 인터페이스와 위임(Delegation) |
--- ## 4. 실전 가이드: 언제 무엇을 써야 할까?
무조건 상속이 나쁘고 컴포지션이 좋은 것은 아닙니다. 적재적소에 배치하는 것이 실력입니다.
상속을 사용해도 좋은 경우
- 두 클래스가 진정한 계층 구조를 이룰 때 (예: 포유류 -> 고양이)
- 부모 클래스의 API에 결함이 없거나, 결함이 자식 클래스까지 전파되어도 무방할 때
- 자식 클래스가 부모 클래스의 진정한 하위 타입인 경우 (Liskov Substitution Principle 준수)
컴포지션을 권장하는 경우
- 단순히 코드 재사용이 목적인 경우
- 클래스의 세부 구현이 자주 변경될 가능성이 있는 경우
- 다양한 기능을 조합하여 새로운 기능을 만들어야 할 경우
--- ## 5. 결론: 상속보다는 조합을 우선하라 (Favor Composition over Inheritance)
이 원칙은 디자인 패턴의 고전인 GoF의 디자인 패턴에서 강조하는 핵심 문구입니다. 상속은 강력하지만 변화에 취약한 '정적인 관계'인 반면, 컴포지션은 유연하고 확장성이 높은 '동적인 관계'를 가능하게 합니다. 여러분의 Java 코드에서 extends가 보인다면, 한 번쯤 "이것이 진정한 is-a 관계인가?"를 고민해 보시기 바랍니다.
내용 출처:
- Effective Java 3rd Edition (Joshua Bloch 저) - Item 18: 상속보다는 컴포지션을 사용하라
- GoF의 디자인 패턴 (Erich Gamma 외 저)
- Oracle Java Documentation - Object-Oriented Programming Concepts
'Language > Java' 카테고리의 다른 글
| [JAVA] Java 인터페이스 변수 선언 시 자동으로 붙는 키워드의 비밀: 왜 public static final인가? (0) | 2026.01.16 |
|---|---|
| [JAVA] 추상 메서드 없는 추상 클래스, 왜 그리고 언제 사용할까? (0) | 2026.01.16 |
| [JAVA] Getter와 Setter를 사용하는 이유 : 객체지향의 꽃, 캡슐화 완성하기 (0) | 2026.01.16 |
| [JAVA] toString() 메서드의 진정한 가치와 실무적 활용법 (0) | 2026.01.16 |
| [JAVA] hashCode()를 반드시 오버라이드해야 하는 이유 : 데이터 무결성의 핵심 (0) | 2026.01.16 |