본문 바로가기

JAVA

상속과 조합: 상속은 아는데, 조합은 뭐지..?

JAVA를 하는 사람들이라면 당연히 알고 있을 '상속'의 개념과 함께 워딩 자체는 생소한'조합'의 개념도 살펴보자!

(사실 우린 둘 다 쓰고있다! 쫄지말자!)


상속

기존에 정의되어 있는 클래스의 필드와 메소드를 물려받아 새로운 클래스를 생성하는 기법

https://mantaray.tistory.com/69

상속의 특징: 2가지

  1. 중복코드 제거와 기능 확장을 쉽게 할 수 있다.
  2. 클래스들의 계층적인 구조를 만들 있다.

상속의 대표적인 문제점: 2가지 (캡슐화)

  1. 하위 클래스가 상위 클래스의 구현에 의존하기 때문에 변경에 취약하다
  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 마음대로)도 구현할 수 있다.

상속은 불필요한 부모 클래스의 퍼블릭 메서드가 자식 클래스도 어쩔 없이 노출하게 된다.

  • 특히 공개된 부모 클래스의 퍼블릭 메서드가 자식 클래스의 내부 규칙과 맞지 않을 있다.

조합

조합은 전체를 표현하는 클래스가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용하는 방법

https://mantaray.tistory.com/69

조합의 장점

  1. 상속과 달리 부분 객체의 내부 구현이 공개되지 않는다.
  2. 메서드 호출하는 방식으로 퍼블릭 인터페이스에 의존해서 부분 객체의 내부 구현이 변경되어도 비교적 안전하다.
  3. 부분 객체의 모든 퍼블릭 메서드를 공개하지 않아도 된다.

 

그래서 조합은 어떻게 하는거지..?

조합하고 싶은 클래스의 인스턴스를 새로운 클래스의 private 필드로 참조! 다음 인스턴스의 메서드를 호출하는 방식으로 구현!

// 앞서 살펴봤던 WinningLotto 클래스가 Lotto를 상속하는 것이 아닌 조합(Composition)을 사용하면 다음과 같다.
public class WinningLotto {
    private Lotto lotto;
    private BonusBall bonusBall;
}

이처럼 WinningLotto 클래스에서 인스턴스 변수로 Lotto 클래스를 가지는 것이 조합(Composition)이다.
WinningLotto 클래스는 Lotto 클래스의 메서드를 호출하는 방식으로 동작하게 된다.


상속과 조합의 차이

구분 상속 Inheritance 조합 Composition
의존성 해결시점 컴파일 런타임
결합도 높음
- 부모 클래스의 기능 변경에 의해 자식 클래스가 영향을 많이 받음.
닞음

상속과 조합을 어떨 때 사용할까?

상속의 목적

  1. 서브타이핑: 다형적인 계층구조 구현 (부모와 자식 행동이 호환)
  2. 서브클래싱: 다른클래스의 코드를 재사용 (부모와 자식 행동이 호환 X)

상속을 고려하기 전에 물어볼 질문들

  1. 상속을 고려하는 객체가 서로 IS-A 관계인가?
  2. 클라이언트 관점에서 객체가 동일한 행동을 것이라 기대하는가?

상속과 조합 결정 기준

  1. 단순히 코드를 재사용하고 싶다! -> 조합
  2. 동일하게 행동하는 인스턴스를 그룹화 하고싶다! -> 상속

참고

 

상속보다는 조합(Composition)을 사용하자.

tecoble.techcourse.co.kr