**LSP(Liskov Substitution Principle)**는 1988년 Barbara Liskov가 정의한 원칙이다. 처음에는 상속에 관한 원칙으로 보이지만, 실제로는 인터페이스와 구현에 관한 더 넓은 설계 원칙이다.
LSP의 정의
Barbara Liskov의 원래 정의 (1988)
“S형의 객체 o1 각각에 대응하는 T형 객체 o2가 있고, T로 정의된 모든 프로그램 P에서 o2를 o1으로 치환해도 P의 행동이 변하지 않으면, S는 T의 하위 타입이다.”
쉽게 말하면:
“부모 타입을 사용하는 곳에 자식 타입을 넣어도 프로그램이 올바르게 동작해야 한다.”
행위의 일관성
LSP의 핵심은 행위의 일관성이다. 하위 타입은:
- 상위 타입의 모든 기대를 충족해야 한다
- 상위 타입의 계약을 지켜야 한다
- 상위 타입 대신 사용해도 문제가 없어야 한다
정사각형/직사각형 문제
LSP를 설명할 때 가장 유명한 예제다.
직관적이지만 잘못된 상속
수학적으로 정사각형은 직사각형의 특수한 경우다. 그래서 다음과 같이 설계하는 것이 자연스러워 보인다:
| |
문제 발생
| |
flowchart TB
subgraph Problem [LSP 위반]
R[Rectangle]
S[Square]
S -->|상속| R
Code["resize(Rectangle r)r.setWidth(5)r.setHeight(4)assert area == 20"]
Code -->|Rectangle 전달| OK[성공: 20]
Code -->|Square 전달| FAIL[실패: 16]
end
왜 위반인가?
Rectangle의 계약은 다음과 같다:
setWidth()는 너비만 변경setHeight()는 높이만 변경- 둘은 독립적
Square는 이 계약을 위반한다. 따라서 Square는 Rectangle의 올바른 하위 타입이 아니다.
해결책
| |
Square와 Rectangle은 별개의 타입으로, 공통 인터페이스만 공유한다.
LSP는 상속에만 적용되지 않는다
인터페이스 구현에도 적용
LSP는 extends 뿐 아니라 implements에도 적용된다:
| |
License를 사용하는 코드에서 PersonalLicense와 BusinessLicense를 치환해도 문제가 없어야 한다.
덕 타이핑에도 적용
타입이 명시되지 않는 동적 언어에서도 LSP는 적용된다:
| |
아키텍처 수준의 LSP
택시 배차 시스템 예제
마틴은 택시 배차 시스템 예제를 사용한다.
요구사항
여러 택시 회사의 REST API를 호출하여 배차:
| |
flowchart TB
D[배차 시스템]
T1[택시 회사 A]
T2[택시 회사 B]
T3[택시 회사 C]
D -->|REST API| T1
D -->|REST API| T2
D -->|REST API| T3
LSP 위반
택시 회사 C가 다른 API를 사용한다면?
| |
이제 배차 시스템은 특별 처리가 필요하다:
| |
문제점
- if 문 추가: 새 택시 회사마다 조건 추가
- 버그 위험: 특별 처리 누락 시 버그
- 확장성 저하: OCP 위반
해결책
| |
flowchart TB
D[배차 시스템]
I[TaxiApi Interface]
A1[StandardAdapter]
A2[CompanyCAdapter]
T1[택시 회사 A/B]
T2[택시 회사 C]
D --> I
A1 -->|구현| I
A2 -->|구현| I
A1 --> T1
A2 --> T2
계약에 의한 설계 (Design by Contract)
사전 조건 (Precondition)
메서드 호출 전에 만족해야 하는 조건:
| |
LSP: 하위 타입은 사전 조건을 더 약하게 할 수 있지만, 더 강하게 하면 안 된다.
사후 조건 (Postcondition)
메서드 실행 후 만족해야 하는 조건:
| |
LSP: 하위 타입은 사후 조건을 더 강하게 할 수 있지만, 더 약하게 하면 안 된다.
불변식 (Invariant)
객체가 항상 만족해야 하는 조건:
| |
LSP: 하위 타입은 상위 타입의 불변식을 반드시 유지해야 한다.
LSP 위반 징후
코드에서 다음이 보이면 LSP 위반을 의심하라:
1. 타입 체크
| |
2. 빈 구현
| |
3. 예외 던지기
| |
핵심 요약
| 항목 | 내용 |
|---|---|
| 정의 | 하위 타입은 상위 타입을 대체할 수 있어야 함 |
| 핵심 | 행위의 일관성, 계약 준수 |
| 적용 범위 | 상속, 인터페이스, REST API 등 |
| 위반 징후 | 타입 체크, 빈 구현, 예외 던지기 |
“LSP는 아키텍처 수준까지 확장할 수 있고, 반드시 확장해야만 한다. 치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 특별한 메커니즘으로 오염되기 때문이다.” — Robert C. Martin
다음 장에서는
다음 장에서는 ISP: 인터페이스 분리 원칙을 다룬다. 이 원칙은 클라이언트가 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리해야 한다는 것을 말한다.
![Featured image of post [Clean Architecture] 17. LSP: 리스코프 치환 원칙](/post/cleanarchitecture/17-lsp-liskov-substitution-principle/wordcloud_hu_5f1f728ddf413e2.png)
![[Clean Architecture] 15. SRP: 단일 책임 원칙](/post/cleanarchitecture/15-srp-single-responsibility-principle/wordcloud_hu_8a6fa0e5a13a91ff.png)
![[Clean Architecture] 16. OCP: 개방-폐쇄 원칙](/post/cleanarchitecture/16-ocp-open-closed-principle/wordcloud_hu_a84054017b47f51d.png)
![[Clean Architecture] 17. LSP: 리스코프 치환 원칙](/post/cleanarchitecture/17-lsp-liskov-substitution-principle/wordcloud_hu_494cd6bbbdcbeefe.png)
![[Clean Architecture] 18. ISP: 인터페이스 분리 원칙](/post/cleanarchitecture/18-isp-interface-segregation-principle/wordcloud_hu_e792044eccd81103.png)
![[Clean Architecture] 19. DIP: 의존성 역전 원칙](/post/cleanarchitecture/19-dip-dependency-inversion-principle/wordcloud_hu_b2315221135160e2.png)
![[Clean Architecture] 14. SOLID 원칙 서론](/post/cleanarchitecture/14-solid-principles-introduction/wordcloud_hu_b40e2016719b5d89.png)