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

  1. 본질: LSP (Liskov Substitution Principle)는 자식 클래스가 부모 클래스의 역할을 완벽히 대신할 수 있어야 하며, 치환하더라도 프로그램의 정확성이 깨지지 않아야 한다는 객체지향 설계 원칙이다.
  2. 가치: 인터페이스나 부모 클래스가 보장하는 계약(Contract)을 자식 클래스가 무조건 준수하게 강제함으로써, 예상치 못한 런타임 오류를 막고 다형성 (Polymorphism)을 안전하게 구현하게 해준다.
  3. 판단 포인트: '직사각형을 상속받은 정사각형'이나 '새를 상속받은 펭귄'처럼 현실 세계의 분류 체계로는 맞더라도, 행위(Behavior) 측면에서 부모의 규약을 위반한다면 상속을 분리하거나 합성을 사용해야 한다.

Ⅰ. 개요 및 필요성

리스코프 치환 원칙 (LSP, Liskov Substitution Principle)은 바바라 리스코프(Barbara Liskov)가 1987년에 제안한 개념으로, 객체지향 프로그래밍의 5대 원칙인 SOLID의 'L'을 담당한다. 이 원칙은 "상위 타입의 객체를 하위 타입의 객체로 치환해도 소프트웨어의 실행에는 아무런 문제가 없어야 한다"고 정의한다.

LSP가 없다면 다형성을 활용한 코드 확장이 불가능해진다. 클라이언트 코드는 부모 클래스의 메서드를 믿고 호출하는데, 자식 클래스에서 이 메서드의 동작을 멋대로 바꾸거나 예외를 던진다면 시스템은 예측 불가능한 상태에 빠진다. 이는 코드 곳곳에 객체의 타입을 확인하는 지저분한 조건문(if-else, instanceof)을 양산하게 만들고, 결국 유지보수성을 크게 떨어뜨리는 원인이 된다.

  • 📢 섹션 요약 비유: 카페 메뉴판에 '아메리카노'라고 적혀 있다면, 알바생이 새로 왔든 점장이 만들든 손님은 쓴맛의 커피를 기대합니다. 만약 새 알바생이 아메리카노 주문에 오렌지 주스를 내어준다면, 카페의 신뢰(규약)가 깨지는 것이 바로 LSP 위반입니다.

Ⅱ. 아키텍처 및 핵심 원리

LSP의 핵심은 계약에 의한 설계 (Design by Contract)를 지키는 것이다. 자식 클래스는 부모 클래스가 정의한 사전 조건 (Preconditions)을 강화해서는 안 되며, 사후 조건 (Postconditions)을 약화시켜서도 안 된다.

대표적인 위반 사례가 '새(Bird)'와 '펭귄(Penguin)'의 관계다.

┌──────────────────────────────────────────────────────────────┐
│                  LSP 위반과 준수의 아키텍처 비교             │
├──────────────────────────────────────────────────────────────┤
│ [LSP 위반 구조: 행위 불일치]      [LSP 준수 구조: 인터페이스 분리] │
│                                                              │
│       << Bird >>                     << Bird >>              │
│       + fly()                        + eat()                 │
│          ▲                              ▲                    │
│          │                              ├───┐                │
│    ┌─────┴─────┐                  ┌─────┴─────┐              │
│    │           │                  │           │              │
│ Sparrow     Penguin(위반!)    << Flyable >>  Penguin         │
│ + fly()     + fly() -> 예외!    + fly()      + eat()         │
│                                   ▲                          │
│                                   │                          │
│                                Sparrow                       │
│                                + fly()                       │
│                                                              │
└──────────────────────────────────────────────────────────────┘

왼쪽 구조에서 클라이언트는 Bird 타입의 객체 리스트를 순회하며 fly()를 호출할 때, Penguin 객체를 만나면 프로그램이 크래시(Crash)된다. 자식이 부모의 '날 수 있다'는 행위 계약을 위반했기 때문이다. 오른쪽 구조처럼 비행 가능성(Flyable)을 분리하여 재설계하면, 치환 과정에서 발생하는 논리적 모순을 원천적으로 차단할 수 있다.

  • 📢 섹션 요약 비유: 렌터카 회사에서 '승용차'를 빌렸습니다. 아반떼를 받든 쏘나타를 받든 운전자는 엑셀을 밟으면 차가 앞으로 갈 것(계약)을 기대합니다. 그런데 장난감 자동차를 주고 "이것도 차니까 타세요"라고 한다면 계약 위반입니다.

Ⅲ. 비교 및 연결

LSP는 SOLID의 다른 원칙들, 특히 개방-폐쇄 원칙 (OCP, Open-Closed Principle)을 달성하기 위한 필수 기반 구조 역할을 한다.

비교 항목LSP (리스코프 치환 원칙)OCP (개방-폐쇄 원칙)
핵심 목적다형성의 신뢰성 보장 (올바른 상속 구조)시스템의 유연한 확장 보장 (수정 없는 확장)
초점하위 클래스가 상위 클래스의 계약을 완벽히 대체하는가?기존 코드의 수정 없이 새로운 기능 추가가 가능한가?
위반 시 결과런타임 에러 발생, 클라이언트의 타입 체크(instanceof) 남발기능 추가 시마다 기존 로직(if-else)을 계속 뜯어고쳐야 함
상호 관계OCP를 성립시키기 위한 구조적 전제 조건LSP가 완벽히 지켜질 때 비로소 완성되는 아키텍처의 목표

결국 자식 클래스가 부모를 안전하게 대체(LSP)할 수 있어야만, 기존 코드를 수정하지 않고도 새로운 자식 클래스를 추가(OCP)하여 기능을 확장할 수 있다. 두 원칙은 다형성이라는 같은 동전의 양면과 같다.

  • 📢 섹션 요약 비유: LSP가 플러그와 콘센트의 규격(모양과 전압)을 정확히 일치시키는 규칙이라면, OCP는 그 규격만 맞으면 선풍기든 냉장고든 벽을 뜯지 않고 무한히 꽂아 쓸 수 있게 해주는 확장성입니다.

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

실무에서 소프트웨어를 설계하거나 코드 리뷰를 할 때, LSP 위반을 찾아내고 리팩토링하는 판단 기준은 명확하다.

  1. 타입 체크의 제거: 코드 내부에 if (obj instanceof Penguin)처럼 특정 하위 타입을 런타임에 확인하고 분기하는 코드가 있다면, 이는 LSP가 깨졌다는 강력한 경고(Smell)다. 다형성으로 해결하도록 구조를 개편해야 한다.
  2. NotImplementedException 회피: 상속받은 메서드 내부에 "지원하지 않는 기능입니다"라며 예외를 던지거나 빈 블록으로 방치한다면 상속을 잘못 쓴 것이다. 이 경우 상속(IS-A)을 포기하고 합성 (HAS-A, Composition)을 사용하거나 인터페이스를 분리(ISP)해야 한다.
  3. 계약 테스트 (Contract Testing): 부모 클래스를 기준으로 작성된 단위 테스트 (Unit Test)를 하위 클래스에 그대로 적용했을 때 모두 통과해야 한다. 하나라도 실패하면 치환 불가능한 설계다.
  • 📢 섹션 요약 비유: 건전지 장난감 차에 'AA 건전지'를 넣으라고 적혀 있다면, 듀라셀이든 에너자이저든 차는 똑같이 잘 움직여야 합니다(테스트 통과). 건전지 브랜드를 가려서 고장 난다면 장난감이나 건전지 중 하나가 불량인 것입니다.

Ⅴ. 기대효과 및 결론

리스코프 치환 원칙을 철저히 준수하면 시스템은 런타임 오류로부터 안전해지고, 객체 간의 결합도가 낮아진다. 클라이언트 코드는 객체의 실제 구현 타입이 무엇인지 알 필요 없이 오직 상위 인터페이스만 보고 협력할 수 있게 되어 코드의 재사용성과 가독성이 극대화된다.

그러나 현실 세계의 개념적 계층(예: 정사각형은 직사각형이다)을 객체지향의 행위적 계층에 억지로 끼워 맞추려 할 때 LSP 위반이 자주 일어난다. 결론적으로 LSP는 "객체지향에서의 상속은 현실의 분류 체계가 아니라, 소프트웨어 내부의 행위와 계약의 일치 여부로 결정되어야 한다"는 날카로운 통찰을 제공한다.

  • 📢 섹션 요약 비유: 좋은 배우(자식 객체)는 대본(부모 클래스 규약)의 흐름을 깨지 않는 선에서 자신만의 연기를 펼칩니다. 대본을 완전히 무시하고 혼자 다른 장르를 연기해 극을 망치는 배우는 무대(다형성)에 오를 자격이 없습니다.

📌 관련 개념 맵

개념연결 포인트
다형성 (Polymorphism)하나의 인터페이스로 다양한 하위 객체를 일관되게 다루는 객체지향의 특성
계약에 의한 설계 (Design by Contract)컴포넌트 간의 권리와 책임을 명확한 규약(사전/사후/불변 조건)으로 정의하는 기법
OCP (Open-Closed Principle)확장에 열려있고 수정에 닫혀있는 원칙으로, LSP 준수 시 실현 가능함
상속 (Inheritance) vs 합성 (Composition)LSP를 위반하는 무리한 상속 대신, 유연하게 기능을 조합하는 대안적 설계 방식

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

객체지향 프로그래밍 (OOP) · 다형성 개념의 등장
    │
    ▼
상속의 오남용 발생 (행위 불일치 및 런타임 에러)
    │
    ▼
계약에 의한 설계 (Design by Contract) · 규약의 중요성 대두
    │
    ▼
LSP (Liskov Substitution Principle) · 치환 가능성 정의
    │
    ▼
SOLID 원칙 정립 · OCP, ISP와의 결합을 통한 유연한 아키텍처 완성

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

  1. 건전지 장난감 차에 'AA 건전지'를 넣으라고 적혀 있어요.
  2. 어떤 브랜드의 AA 건전지를 넣든 장난감 차는 똑같이 잘 움직여야 정상이에요.
  3. 만약 어떤 건전지를 넣었을 때 차가 고장 난다면, 그 건전지는 'AA 건전지의 역할'을 제대로 못한 것이고 이게 바로 리스코프 치환 원칙을 어긴 거랍니다!