자바 디자인 패턴 용어 사전
원칙상속보다 합성 · GoF 2대 원칙

합성 vs 상속

GoF가 제시한 두 원칙 중 하나. 대부분의 패턴이 상속 대신 합성으로 유연성을 얻는다.

GoF가 책 서두에 제시한 2대 설계 원칙 중 하나 — "클래스 상속보다 객체 합성을 선호하라".

(다른 하나는 "구현이 아니라 인터페이스에 프로그래밍하라".)

왜 상속이 위험한가

  • ① 컴파일 타임에 고정된다 — 런타임에 바꿀 수 없다
  • ② 캡슐화를 깬다 — 하위가 상위의 구현 세부에 의존한다
  • ③ 조합이 폭발한다 — 축이 둘이면 클래스가 곱해진다
  • ④ 상위 변경이 모든 하위를 깨뜨린다 (fragile base class)
// ✗ 상속 — 커피 옵션 조합마다 클래스가 필요하다
class CoffeeWithMilk extends Coffee {}
class CoffeeWithMilkAndSugar extends CoffeeWithMilk {}   // 조합 폭발

// ✓ 합성 — 런타임에 감싼다 (Decorator)
Beverage b = new Sugar(new Milk(new Coffee()));

패턴들이 이 원칙의 구현이다

  • Decorator — 상속 대신 감싸서 기능 추가
  • Strategy — 상속 대신 알고리즘 객체 교체
  • Bridge — 상속 대신 구현 객체 참조 → 곱셈을 덧셈으로
  • Composite — 상속 대신 자식 목록 보유

반대로 상속을 쓰는 패턴도 있다

  • Template Method — 골격을 상위에 고정하고 훅만 하위가 채운다
    • 변하는 부분이 '메서드 일부' 로 작을 때는 상속이 간결하다

상속이 맞는 경우

  • 진짜 is-a 관계다 (하위가 상위를 완전히 대체 가능 — 리스코프 치환)

  • 상위가 상속을 위해 설계·문서화되어 있다

  • 같은 패키지 안이라 상위 변경을 통제할 수 있다

  • 판별 "B 는 A 다" 가 어색하면 상속이 아니라 합성이다

    • Stack 이 Vector 를 상속한 것이 대표적 실패 사례 (Stack 은 Vector 가 아니다)

면접 함정

  • "상속은 나쁘다" → 나쁜 게 아니라 결합이 강하다. Template Method처럼 적합한 자리가 있다.
  • 코드 재사용 목적의 상속 → 재사용은 합성으로 한다. 상속은 타입 계층을 만들 때 쓴다.

상속이 캡슐화를 깨는 구체적 예

class CountingSet<E> extends HashSet<E> {
    private int addCount;
    public boolean add(E e) { addCount++; return super.add(e); }
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);      // 💥 내부에서 add() 를 호출한다
    }
}
new CountingSet<>().addAll(List.of(1,2,3));   // addCount 가 6 이 된다

상위 클래스의 구현 세부(addAll이 add를 부른다)에 의존하게 됐다. 그 구현이 바뀌면 하위가 조용히 깨진다.

위임 보일러플레이트

합성의 대가는 위임 코드다 — Set 의 메서드 15개를 전부 넘겨 써야 한다

  • 완화 — IDE 의 delegate 생성 · 추상 위임 클래스 재사용(ForwardingSet)
    • 코틀린은 by 키워드로 언어가 지원한다

결정 순서

  • ① is-a 가 성립하는가? (하위를 상위 자리에 넣어도 되는가) 아니오 → 합성
  • 상위가 상속을 위해 설계·문서화되었는가? — 아니오 → 합성
  • 변하는 축이 둘 이상인가? — 예 → 합성 (Bridge)
  • 골격은 같고 일부만 다른가? — 예 → 상속 OK (Template Method)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 원칙 — 패턴이 서 있는 바닥