핵심 인사이트 (3줄 요약)

  1. 본질: 상속 거부 (Refused Bequest) 는 자식 클래스가 부모의 메서드나 속성을 사용하지 않거나 빈 구현으로 오버라이드함으로써 리스코프 치환 원칙 (LSP: Liskov Substitution Principle) 을 위반하는 패턴이다.
  2. 가치: "is-a 관계"가 실제로는 성립하지 않는 상속을 발견·제거함으로써 시스템의 다형성 (Polymorphism) 신뢰성을 회복한다.
  3. 판단 포인트: "부모 타입 변수에 자식 인스턴스를 대입해도 프로그램이 올바르게 동작하는가?" — No라면 LSP 위반이다.

Ⅰ. 개요 및 필요성

바바라 리스코프 (Barbara Liskov) 가 1987년 제안한 원칙으로, "서브타입은 그것의 베이스타입으로 치환 가능해야 한다"는 원칙이다. 즉, T 타입의 객체가 요구되는 모든 곳에 S extends T 타입의 객체를 대입해도 프로그램의 정확성이 변하지 않아야 한다.

[ 상속 거부 사례 — 정사각형/직사각형 문제 ]
┌────────────────────────────────────────────────────────┐
│  class Rectangle {                                     │
│    int width, height;                                  │
│    void setWidth(int w)  { this.width = w; }           │
│    void setHeight(int h) { this.height = h; }          │
│    int  area()           { return width * height; }    │
│  }                                                     │
│                                                        │
│  class Square extends Rectangle {  ← "is-a?" NO!      │
│    @Override                                           │
│    void setWidth(int w) {                              │
│      this.width = w; this.height = w;  // 거부: height │
│    }                                                   │
│    @Override                                           │
│    void setHeight(int h) {                             │
│      this.width = h; this.height = h;  // 거부: width  │
│    }                                                   │
│  }                                                     │
└────────────────────────────────────────────────────────┘
  Rectangle r = new Square(5);
  r.setWidth(3);   // height도 3으로 변경 → 기대 위반!
  assert r.area() == 15;  ← FAIL (실제: 9)

다형성 (Polymorphism) 을 활용하는 코드는 부모 타입으로 객체를 다룬다. LSP가 위반되면 런타임에 예상치 못한 동작이 발생하고, 타입 체크 (instanceof) 코드가 급증해 OCP (개방-폐쇄 원칙, Open-Closed Principle) 도 함께 위반된다.

  • 📢 섹션 요약 비유: "포유류" 카드게임에서 "고래"를 뽑았더니 "육지에서 달려라" 명령을 수행하지 못한다 — 상속 계층이 잘못 설계된 것이다.

Ⅱ. 아키텍처 및 핵심 원리

┌────────────────────────────────────────────────────────────┐
│               LSP 위반 유형 분류                           │
├────────────────────┬───────────────────────────────────────┤
│  유형              │  설명 및 예시                         │
├────────────────────┼───────────────────────────────────────┤
│ 사전 조건 강화     │ 자식이 더 엄격한 입력 조건 요구       │
│ (Precondition      │ 부모: accept(n >= 0)                  │
│  Strengthening)    │ 자식: accept(n > 0) ← 위반            │
├────────────────────┼───────────────────────────────────────┤
│ 사후 조건 약화     │ 자식이 더 약한 결과 보장              │
│ (Postcondition     │ 부모: return list non-empty           │
│  Weakening)        │ 자식: return null 가능 ← 위반         │
├────────────────────┼───────────────────────────────────────┤
│ 불변식 위반        │ 자식이 클래스 불변 조건 파괴          │
│ (Invariant         │ Square가 width≠height 상태 허용       │
│  Violation)        │                                       │
├────────────────────┼───────────────────────────────────────┤
│ 예외 규칙 추가     │ 자식이 부모가 던지지 않는 예외 추가   │
│ (Exception         │ 자식 override에서 새 예외 throw       │
│  Addition)         │                                       │
└────────────────────┴───────────────────────────────────────┘
[ 처방 — 구성 방식 재설계 ]
┌────────────────────────────────────────────────┐
│  interface Shape { int area(); }               │
│                                                │
│  class Rectangle implements Shape {           │
│    int width, height;                          │
│    int area() { return width * height; }       │
│  }                                             │
│                                                │
│  class Square implements Shape {              │
│    int side;                                   │
│    int area() { return side * side; }          │
│  }                                             │
│  // Rectangle과 Square는 더 이상 상속 관계 없음 │
└────────────────────────────────────────────────┘
항목설명포인트
핵심 역할입력·상태·출력을 분리하는 책임 경계구현보다 경계를 먼저 본다.
제어 지점조건, 이벤트, 정책이 만나는 곳병목과 결합이 생기는 곳이다.
검증 포인트테스트·로그·모니터링으로 확인할 지점운영 가능성이 설계 품질을 결정한다.
  • 📢 섹션 요약 비유: 스마트폰이 "전화기"를 상속받아 "팩스 기능 거부"를 오버라이드하는 것보다, 스마트폰이 전화 기능을 "내장"하고 별도로 존재하는 게 더 자연스럽다.

Ⅲ. 비교 및 연결

구분상속 (Inheritance)구성 (Composition)
관계is-ahas-a
결합도높음 (부모 변경 → 자식 영향)낮음 (인터페이스 변경만 영향)
유연성낮음 (컴파일 타임 결정)높음 (런타임 교체 가능)
LSP 위반 위험높음낮음
권장 원칙is-a 관계 명확할 때만기본값으로 구성 선택

상속 거부 (Refused Bequest) 는 여러 SOLID 원칙과 동시에 충돌한다.

SOLID 원칙위반 방식
LSP (리스코프 치환 원칙)직접 위반 — 치환 불가
OCP (개방-폐쇄 원칙)타입 체크 코드 급증
ISP (인터페이스 분리 원칙)불필요한 메서드 강제 구현
  • 📢 섹션 요약 비유: 채식주의자를 "인간"의 서브클래스로 만들고 "고기 먹기" 메서드를 오버라이드해 비어두는 것 — 생물학 교과서가 틀린 게 아니라 설계가 틀린 것이다.

Ⅳ. 실무 적용 및 기술사 판단

Java 표준 라이브러리의 Stack<E>Vector<E> 를 상속하는 것은 역사적 LSP 위반 사례다. Stackadd(index, element) 같은 Vector 메서드를 호출하면 스택의 불변식(후입선출, LIFO: Last-In-First-Out)이 깨진다. 현재는 Deque 인터페이스와 ArrayDeque 구현을 사용하도록 권고한다.

  • 빈 메서드 구현: 오버라이드 메서드 내부가 비어있거나 UnsupportedOperationException만 던지면 신호

  • 상속 계층 깊이: 3단 이상 상속은 잠재적 LSP 위반 가능성이 높음

  • 코드 리뷰 체크: @Override 메서드에서 throw new UnsupportedOperationException 검색

  • 상속 계층 재설계: "상속 → 인터페이스 + 구성" 전환을 설계 개선 방안으로 제시

  • 계약 설계 (Design by Contract, DbC): 선행 조건 (Precondition), 후행 조건 (Postcondition), 불변식 (Invariant) 문서화

  • 인터페이스 분리 (ISP: Interface Segregation Principle): 거대 인터페이스 대신 역할별 소형 인터페이스

판단 체크리스트

  1. 변경 전 동작을 고정할 테스트가 준비되었는가?
  2. 냄새의 원인이 구조 문제인지 일회성 구현인지 구분했는가?
  3. 리팩토링 단위를 작게 나눠 롤백 가능하게 했는가?
  4. 명명·모델·패키지 경계가 함께 개선되는가?
  • 📢 섹션 요약 비유: 알바생을 뽑을 때 "모든 업무 가능자"로 채용했는데 특정 업무는 아예 못 한다면, 처음부터 역할을 나눠 채용했어야 한다 — 인터페이스 분리 원칙이다.

Ⅴ. 기대효과 및 결론

지표LSP 위반 유지LSP 준수
instanceof 체크 코드 수많음없음
다형성 적용 가능 범위제한적전체 계층
새 서브타입 추가 비용높음 (기존 체크 코드 수정)낮음 (새 클래스만)
런타임 예외 발생률높음낮음

상속 거부 (Refused Bequest) 는 "is-a 관계를 착각한 설계"의 결과다. LSP (Liskov Substitution Principle) 는 다형성이 실제로 작동하기 위한 논리적 전제 조건이다. 실무에서 상속 계층을 설계할 때는 "이 자식 클래스가 부모의 모든 계약을 이행하는가?"를 먼저 검토하고, 의심스러우면 구성 (Composition) 을 선택해야 한다.

확장 방향은 ① 정적 분석 자동화, ② 아키텍처 적합성 검증, ③ 작은 단위의 상시 리팩토링 문화 정착이다.

  • 📢 섹션 요약 비유: 운전면허증이 있으면 모든 차를 운전할 수 있어야 한다 — 특정 차(서브클래스)는 면허(계약)를 무시하고 싶다면 면허 제도 자체가 무의미해진다.

📌 관련 개념 맵

관계개념설명
상위 개념코드 스멜 (Code Smell)상속 거부는 주요 스멜
상위 개념SOLID 원칙LSP는 SOLID의 'L'
핵심 원칙LSP (Liskov Substitution Principle)치환 가능성 보장
연관 원칙OCP (Open-Closed Principle)LSP 위반 시 함께 위반
연관 원칙ISP (Interface Segregation Principle)불필요 메서드 강제 방지
처방구성 우선 (Composition over Inheritance)상속 대신 구성
연관 개념계약 설계 (Design by Contract, DbC)선행/후행 조건, 불변식

📈 관련 키워드 및 발전 흐름도

상속 오남용 → 상속 거부와 LSP 위반 → 조합 중심 설계

👶 어린이를 위한 3줄 비유 설명

  1. 모든 새는 날 수 있다고 배웠는데, 타조는 날지 못한다 — 타조를 "날 수 있는 새" 서브클래스로 만들면 거짓말이 된다.
  2. "새" 카테고리에서 "날기" 기능은 선택 사항으로 분리하거나, 타조는 다른 카테고리에 넣어야 한다.
  3. 상속 거부 제거는 이처럼 "진짜 is-a 관계인지" 다시 확인하는 과정이다.