📚 Chapter 2.1.3. LSP (Liskov Substitution Principle) – 리스코프 치환 원칙
✅ LSP란?
"자식 클래스는 부모 클래스를 대체할 수 있어야 한다."
(Subtypes must be substitutable for their base types)
- 부모 클래스를 사용하는 코드가
- 자식 클래스로 바꿔 끼웠을 때도
- 아무 문제 없이 동작해야 한다!
📚 왜 LSP가 중요한가?
문제 상황 |
결과 |
자식 클래스가 부모 규칙을 깨뜨림 |
프로그램이 비정상 동작함 |
예상치 못한 에러 발생 |
다형성(polymorphism)이 깨짐 |
유지보수 지옥 |
코드 수정할 때 어디 터질지 모름 |
✅ LSP를 지키면 → 다형성이 제대로 작동하고, 시스템 안정성 쭉 상승!
🛠️ 나쁜 예제: LSP 위반
부모 클래스
public class Bird
{
public virtual void Fly()
{
Console.WriteLine("날아간다!");
}
}
자식 클래스
public class Ostrich : Bird
{
public override void Fly()
{
throw new NotSupportedException("타조는 못 날아요!");
}
}
❌ 문제점
- Bird 타입 쓰는 코드가 "당연히 날겠지" 기대
- Ostrich는 Fly() 호출 시 예외 발생
- 부모 계약을 자식이 깨버림 → LSP 위반
🛠️ 좋은 예제: LSP 지킨 버전
1. 설계 수정
public abstract class Bird
{
public string Name { get; set; }
}
public interface IFlyable
{
void Fly();
}
2. 역할에 맞게 분리
public class Sparrow : Bird, IFlyable
{
public void Fly()
{
Console.WriteLine($"{Name}가 날고 있다!");
}
}
public class Ostrich : Bird
{
// 날지 않는 새는 IFlyable 구현 안 함
}
3. 사용하는 코드
List<IFlyable> flyingBirds = new List<IFlyable>
{
new Sparrow { Name = "참새" }
};
foreach (var bird in flyingBirds)
{
bird.Fly();
}
📚 실무 스타일 예시: 결제 처리
부모 클래스 |
자식 클래스 |
PaymentMethod |
CreditCardPayment, PayPalPayment |
✨ LSP 심화 – 실전 설계 꿀팁
상황 |
해결법 |
override 하면서 예외 던진다 |
LSP 위반 의심 |
자식이 부모보다 기능 제한 |
LSP 위반 가능성 |
일부 기능이 불가능한 자식 |
타입 쪼개서 분리 설계 |
🎯 LSP를 지키면 생기는 실무 장점
항목 |
효과 |
안정성 확보 |
다형성 깨지지 않음 |
유지보수 쉬움 |
새 기능 추가해도 기존 코드 무너질 위험 적음 |
테스트 코드 안정화 |
부모 타입 테스트 그대로 자식 타입 테스트 가능 |
✅ LSP 요약
항목 |
설명 |
목표 |
부모 타입을 자식 타입으로 자유롭게 교체 가능해야 함 |
신호 |
자식 클래스가 부모 계약 깨거나, 기능 제한하면 위험 |
해결책 |
추상 클래스 + 인터페이스 조합으로 역할 분리 |