티스토리 뷰
IT/SOLID
LSP - 리스코프 치환 원칙 (Liskov Substitution Principle) : "부모를 대체해도 문제가 없어야 한다"
Stv 2025. 4. 25. 17:25목차
1. LSP란?
리스코프 치환 원칙은 "서브타입은 언제나 자신의 기반 타입(부모 타입)으로 교체할 수 있어야 한다"는 원칙이다.
📌 원칙의 핵심
"자식 클래스는 부모 클래스를 대체해도 동작에 문제가 없어야 한다"
- LSP를 만족하면 클라이언트는 부모 타입(추상 타입)만 보고도 구현체(자식 클래스)에 상관없이 동일한 방식으로 사용할 수 있어야 한다.
2. LSP를 이해하는 질문
- 이 자식 클래스는 진짜 is-a 관계인가?
- 부모 클래스를 사용하는 코드에서 자식 클래스를 넣었을 때, 기능이 깨지지 않는가?
- 다형성을 깨지 않고 안전하게 확장할 수 있는가?
3. LSP 위반 예시 (고전적인 Rectangle vs Square)
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int getArea() { return width * height; }
}
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 정사각형이므로 height도 함께 설정
}
@Override
public void setHeight(int height) {
this.width = height; // height 변경 시 width도 같이 변경
this.height = height;
}
}
📉 LSP 위반 상황
Rectangle rect = new Square();
rect.setWidth(5);
rect.setHeight(10);
System.out.println(rect.getArea()); // 예상? 50 → 실제? 100
➡️ Square는 Rectangle을 상속했지만, 동작이 완전히 달라져서 치환이 불가능함
➡️ LSP 위반 -> 이는 상속을 잘못 쓴 설계의 대표적인 예이다.
🔁 4. LSP를 지키는 구조로 리팩토링
public interface Shape {
int getArea();
}
public class Rectangle implements Shape {
private int width;
private int height;
// 생성자 기반으로 설정
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Square implements Shape {
private int length;
public Square(int length) {
this.length = length;
}
public int getArea() {
return length * length;
}
}
➡️ 이제는 서로 다른 규칙을 가진 Rectangle, Square를 "공통된 인터페이스(Shape)로 묶었기 때문에, 서로 간섭 없이 다형성 유지 + 책임 분리
5. 실무에서 자주 보는 LSP 위반 사례
| 위반 사례 | 문제 |
| 자식 클래스가 부모 메서드의 동작 계약(contract)을 변경 | 호출자 입장에서 기능이 다르게 동작함 |
| 상속 후 일부 메서드만 의미 없는 형태로 override | 호출은 가능하지만, 행동이 예상과 다름 |
| 자식 클래스가 부모의 메서드를 오히려 무력화하거나 예외 throw | 인터페이스를 따르지 않는 치환 불가능 상태 |
6. 실무에서 LSP를 지키기 위한 팁
✔️자식 클래스가 부모의 "계약"을 무시하지 않게 하기
- 부모 클래스의 사전 조건(precondition)은 자식이 더 약해야 한다.
- 부모 클래스의 사후 조건(postcondition)은 자식이 더 강해야 한다.
📌 쉽게 말하면 :
"자식은 덜 까다롭게 입력 받고, 더 많이 책임지고 반환하라"
✔️ 진짜 "is-a" 관계일 때만 상속 사용
- 상속은 강력한 결합을 만들어내는 위험한 구조
- 가능하면 상속보다 "구현(implements), 합성(composition)이 더 유연
7. LSP는 OOP와 어떤 관계가 있을까?
| 원칙 | 설명 |
| 다형성 | 부모 타입으로 자식 객체를 대입할 수 있어야 함 |
| 캡슐화 | 자식 클래스가 부모 구조를 잘못 변경하지 않도록 보호 |
| 추상화 | LSP가 보장될 때, 진짜 의미 있는 추상화가 가능해짐 |
✅ 마무리 요약
- LSP는 자식 클래스가 부모 클래스를 대체해도 무방해야 한다는 원칙
- 동작이 다르거나 기능이 일부만 동작하면 치환 불가능 -> 설계 위반
- 진짜 is-a 관계일 때만 상속을 쓰고, 그 외에는 합성(Composition) + 인터페이스 기반 설계를 지향
- LSP는 다형성, 추상화, 테스트 가능성, 유연한 아키텍처의 핵심 기반이다.
🔜 다음 포스팅 예고 : ISP - 인터페이스 분리 원칙 : "클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다" 라는 주제를 다룰 예정이다.
'IT > SOLID' 카테고리의 다른 글
| DIP - 의존성 역전 원칙 (Dependency Inversion Principle) (0) | 2025.04.29 |
|---|---|
| ISP - 인터페이스 분리 원칙(Interface Segregation Principle) (0) | 2025.04.28 |
| OCP - 개방/폐쇄 원칙 (Open/Closed Principle) (0) | 2025.04.24 |
| SRP - 단일 책임 원칙 (Single Responsibility Principle) (0) | 2025.04.21 |
| SOLID 원칙 (2) | 2025.03.11 |