상속보다 컴포지션을 사용해야 하는 이유
상속이란 한 클래스가 다른 클래스를 확장하여 기능을 재사용하고 추가하는 것을 말한다. 반면에 컴포지션은 기존 클래스의 인스턴스를 새로운 클래스의 필드로 포함하여 기능을 사용하는 것을 말한다.
상속이 항상 나쁜 선택일까?
다음과 같은 경우에는 상속을 사용해도 문제가 없다.
- 같은 프로그래머나 팀이 상위 클래스와 하위 클래스를 모두 통제할 수 있을 때
- 상위 클래스가 상속을 고려하여 잘 설계되고 문서화되어 있을 때
- 명확한 is-a 관계가 있을 때 (예: Dog는 Animal이다)
상속의 문제점, 캡슐화의 파괴
캡슐화는 객체의 내부 구현을 숨기고 공개된 인터페이스를 통해서만 상호작용하도록 하는 원칙이다. 상속을 사용할 때 하위 클래스는 상위 클래스의 내부 구현에 의존하게 되어 상위 클래스가 변경되면 하위 클래스에도 영향을 줄 수 있다.
위 코드에서 Dog 클래스는 Animal을 상속하고 makeSound() 메서드를 재정의했다. Animal의 makeSound()는 eatFood()를 호출한다. Dog의 makeSound()에서는 이를 호출하지 않기 때문에 Dog 객체에서 makeSound()를 호출하면 eatFood()가 실행되지 않는다.
만약 eatFood()가 필수적으로 호출되어야 하는 메서드라면 이로 인해 프로그램의 예상치 못한 동작이나 버그가 발생할 수 있다.
이는 상속으로 인해 캡슐화가 깨지는 현상입니다.
컴포지션의 장점
컴포지션은 기존 클래스의 인스턴스를 새로운 클래스의 필드로 포함하고 필요한 기능을 해당 인스턴스에 위임하는 방식이다.
이는 상속보다 더 느슨한 결합도를 가지며 상위 클래스의 내부 구현 변경이 하위 클래스에 직접적인 영향을 미치지 않는다.
위 코드에서 Dog 클래스는 Animal을 상속하지 않고 Animal의 인스턴스를 필드로 포함한다. Dog의 메서드들은 animal 객체의 메서드를 호출하여 기능을 수행한다. 이를 통해 상속의 문제점 없이 Animal의 기능을 재사용할 수 있다.
인터페이스를 통한 추상화
추상 메서드를 강제하기 위해 인터페이스를 사용할 수 있다.
이렇게 하면 AnimalBehavior 인터페이스를 구현함으로써 필요한 메서드를 반드시 구현하도록 강제할 수 있으며 컴포지션을 통해 Animal의 기능을 사용할 수 있다.
상속을 방지하는 방법
Animal 클래스를 상속하지 못하도록 막으면 더욱 안전한 설계를 할 수 있다.
방법 1️⃣, 클래스를 final로 선언
방법 2️⃣, 생성자를 private으로 선언하고 정적 팩토리 메서드 제공
이렇게 하면 외부에서 상속하거나 직접 객체를 생성할 수 없고 정적 메서드를 통해서만 객체를 생성할 수 있다.
상속이 필요한 경우는 언제일까?
상속을 통해 공통 기능을 재사용해야 하는 경우를 생각해 보자.
- 공통 기능의 재사용이 필요한 경우
- 이럴 때도 상속보다는 유틸리티 클래스나 컴포지션을 사용하는 것이 좋다.
- 서비스 로직에 포함하고 싶지 않은 부가 기능이 있을 때
- 예를 들어, 로깅이나 트랜잭션 관리 등이 필요할 수 있다.
- 이런 경우에는 상속보다는 AOP(Aspect-Oriented Programming)를 사용하는 것이 더 적합하다.
- 추상적인 개념을 다양한 방식으로 구현해야 할 때
- 예를 들어, "이동"이라는 추상적인 개념을 "걷기", "달리기" 등으로 구현해야 할 때다.
- 이 경우 인터페이스를 구현하고 컴포지션을 활용하면 더 유연한 설계가 가능하다.
'Dev > Java' 카테고리의 다른 글
『Java』 스레드는 어떻게 메모리를 공유하는가? (0) | 2024.11.22 |
---|---|
『Java』 HashMap은 어떤 원리로 동작하는가 (0) | 2024.11.22 |
『Java』 for-each 문을 사용하기 위한 인터페이스는 무엇인가? (0) | 2024.11.19 |
『Java』 생성자와 static 블록은 무엇이 다를까? (0) | 2024.11.19 |
『Java』 Exception 처리, 맛보기 (0) | 2024.11.08 |