리스코프 치환 원칙(Liskov Substitution Principle, LSP)은 객체 지향 설계 원칙(SOLID) 중 하나로,
Barbara Liskov가 1987년에 제안한 개념이다.
"서브타입은 언제나 기반 타입(Base type)으로 교체할 수 있어야 한다."
즉, 부모 클래스(또는 인터페이스)를 사용하는 코드에서 자식 클래스로 교체해도 문제없이 동작해야 한다는 뜻입니다.
부모 클래스의 객체를 사용하는 자리에 자식 클래스 객체를 넣어도
프로그램의 기능, 의미, 결과가 깨지면 안 된다.
자식 클래스는 부모 클래스의 규약(contract) 을 그대로 지켜야 하며,
부모가 기대하는 동작을 변경하거나 약화시키면 LSP를 위반합니다.
✅ LSP를 지킨 예
class Bird {
void fly() {
System.out.println("날고 있어요!");
}
}
class Sparrow extends Bird {
@Override
void fly() {
System.out.println("참새가 날고 있어요!");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Sparrow(); // 부모 타입으로 참새 대입
bird.fly(); // 정상 동작
}
}
❌ LSP를 위반한 예
class Bird {
void fly() {
System.out.println("날고 있어요!");
}
}
class Penguin extends Bird {
@Override
void fly() {
throw new UnsupportedOperationException("펭귄은 날 수 없어요!");
}
}
Penguin은 Bird의 "날 수 있다" 라는 전제 조건을 깨뜨림
Bird를 기대하는 코드에서 Penguin을 넣으면 예외 발생 → LSP 위반
부모 타입을 기대하는 코드에서 자식 타입을 넣었더니 의미가 달라져서 잘못된 결과가 나옴.
런타임에서만 오류가 드러나 테스트 통과 → 배포 후 장애 같은 상황이 생김.
예:
Bird bird = new Penguin();
bird.fly(); // 예외 발생 → LSP 위반
Penguin이 부모 Bird의 계약("날 수 있다")을 깨버림 → 호출 측에서 예외 처리 필요.
상속의 핵심인 "부모 타입으로 자식 객체를 자유롭게 교체" 가 불가능해짐.
결국 if-else나 instanceof 같은 타입 체크가 늘어나 절차적 코드로 퇴화.
예:
if (bird instanceof Penguin) {
// 펭귄 전용 처리
} else {
bird.fly();
}
→ 이렇게 되면 "상속"을 쓴 의미가 사라짐.
원래는 새로운 자식 클래스를 만들어서 기능 확장 → 기존 코드 변경 없이 사용해야 함.
그런데 LSP 위반 시, 자식 클래스 맞춤 처리를 위해 기존 코드도 함께 수정해야 함.
새로운 서브클래스가 추가될 때마다, 기존 로직을 전부 점검해야 함.
특히 팀 프로젝트에서는 새로운 사람이 들어와서 자식 클래스를 추가하면
원래 정상 동작하던 부분이 깨질 위험이 높음.
LSP가 지켜지면 부모 타입에 대한 테스트를 한 번만 작성하면 됨.
하지만 위반 시, 자식 클래스마다 따로 테스트해야 하고, 경우에 따라서는
부모-자식 간 호환성까지 검증해야 함.