JAVA를 하는 사람들이라면 당연히 알고 있을 '상속'의 개념과 함께 워딩 자체는 생소한'조합'의 개념도 살펴보자!
(사실 우린 둘 다 쓰고있다! 쫄지말자!)
상속
기존에 정의되어 있는 클래스의 필드와 메소드를 물려받아 새로운 클래스를 생성하는 기법
상속의 특징: 2가지
- 중복코드 제거와 기능 확장을 쉽게 할 수 있다.
- 클래스들의 계층적인 구조를 만들 수 있다.
상속의 대표적인 문제점: 2가지 (캡슐화)
- 하위 클래스가 상위 클래스의 구현에 의존하기 때문에 변경에 취약하다
- 상위 클래스의 모든 퍼블릭 메서드가 하위 클래스에도 반드시 노출된다.
취약한 기반 클래스 문제
// 1.로또 번호를 가지는 역할인 Lotto 클래스가 있다.
public class Lotto {
protected List<Integer> lottoNumbers;
public Lotto(List<Integer> lottoNumbers) {
this.lottoNumbers = new ArrayList<>(lottoNumbers);
}
public boolean contains(Integer integer) {
return this.lottoNumbers.contains(integer);
}
...
}
// 2.Lotto 클래스를 상속하는 WinningLotto 클래스를 보자.
public class WinningLotto extends Lotto {
private final BonusBall bonusBall;
public WinningLotto(List<Integer> lottoNumbers, BonusBall bonusBall) {
super(lottoNumbers);
this.bonusBall = bonusBall;
}
public long compare(Lotto lotto) {
return lottoNumbers.stream()
.filter(lotto::contains)
.count();
}
...
}
// 3.현재까진 문제가 없다.
// 4.그러나 Lotto 클래스의 요구사항이 변경되어, 인스턴스 변수인 LIST<Integer>가 int[] lottonNumbers로 바뀌었다고 가정하자.
public class Lotto {
protected int[] lottoNumbers;
public Lotto(int[] lottoNumbers) {
this.lottoNumbers = lottoNumbers;
}
public boolean contains(Integer integer) {
return Arrays.stream(lottoNumbers)
.anyMatch(lottoNumber -> Objects.equals(lottoNumber, integer));
}
...
}
// 부모와 강한 의존을 맺은 WinningLotto 클래스는 강한 영향을 받는다.
public class WinningLotto extends Lotto {
private final BonusBall bonusBall;
// 오류가 발생한다.
public WinningLotto(List<Integer> lottoNumbers, BonusBall bonusBall) {
super(lottoNumbers);
this.bonusBall = bonusBall;
}
// 오류가 발생한다.
public long compare(Lotto lotto) {
return lottoNumbers.stream()
.filter(lotto::contains)
.count();
}
}
불필요한 메소드도 상속받는 문제
Stack과 Vector가 대표적인 예시이다.
- Stack은 Vector를 상속받기에 실제 자료구조와 달리 push외에 add(index 마음대로)도 구현할 수 있다.
상속은 불필요한 부모 클래스의 퍼블릭 메서드가 자식 클래스도 어쩔 수 없이 노출하게 된다.
- 특히 공개된 부모 클래스의 퍼블릭 메서드가 자식 클래스의 내부 규칙과 맞지 않을 수 있다.
조합
조합은 전체를 표현하는 클래스가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용하는 방법
조합의 장점
- 상속과 달리 부분 객체의 내부 구현이 공개되지 않는다.
- 메서드 호출하는 방식으로 퍼블릭 인터페이스에 의존해서 부분 객체의 내부 구현이 변경되어도 비교적 안전하다.
- 부분 객체의 모든 퍼블릭 메서드를 공개하지 않아도 된다.
그래서 조합은 어떻게 하는거지..?
조합하고 싶은 클래스의 인스턴스를 새로운 클래스의 private 필드로 참조! 그 다음 인스턴스의 메서드를 호출하는 방식으로 구현!
// 앞서 살펴봤던 WinningLotto 클래스가 Lotto를 상속하는 것이 아닌 조합(Composition)을 사용하면 다음과 같다.
public class WinningLotto {
private Lotto lotto;
private BonusBall bonusBall;
}
이처럼 WinningLotto 클래스에서 인스턴스 변수로 Lotto 클래스를 가지는 것이 조합(Composition)이다.
WinningLotto 클래스는 Lotto 클래스의 메서드를 호출하는 방식으로 동작하게 된다.
상속과 조합의 차이
구분 | 상속 Inheritance | 조합 Composition |
의존성 해결시점 | 컴파일 | 런타임 |
결합도 | 높음 - 부모 클래스의 기능 변경에 의해 자식 클래스가 영향을 많이 받음. |
닞음 |
상속과 조합을 어떨 때 사용할까?
상속의 목적
- 서브타이핑: 다형적인 계층구조 구현 (부모와 자식 행동이 호환)
- 서브클래싱: 다른클래스의 코드를 재사용 (부모와 자식 행동이 호환 X)
상속을 고려하기 전에 물어볼 질문들
- 상속을 고려하는 두 객체가 서로 IS-A 관계인가?
- 클라이언트 관점에서 두 객체가 동일한 행동을 할 것이라 기대하는가?
상속과 조합 결정 기준
- 단순히 코드를 재사용하고 싶다! -> 조합
- 동일하게 행동하는 인스턴스를 그룹화 하고싶다! -> 상속
참고
- https://youtu.be/U4OSS4jJ9ns
- https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/
- https://mantaray.tistory.com/69
상속보다는 조합(Composition)을 사용하자.
…
tecoble.techcourse.co.kr
'JAVA' 카테고리의 다른 글
JPA N+1 문제: 실무에서 어떻게 다룰까? (0) | 2022.11.09 |
---|---|
Java의 싱글톤과 정적클래스: Spring 마렵다... (1) | 2022.09.22 |
DTO vs VO: 우린 왜 자꾸 나눌까? (0) | 2022.09.14 |