리스코프 교환 원칙(Liskov Substitution Principle)
서브 타입은 언제나 기반 타입으로 교체 가능해야 한다.
서브 타입은 언제나 기반 타입과 호환될 수 있어야 한다.
즉, 기반 클래스는 파생 클래스로 대체할 수 있어야 한다.
부모 클래스 인스턴스 자리에 자식 클래스의 인스턴스가 들어가도 작동해야 한다.
- 자식이 부모 자리에서 작동하려면 부모와 동일하게 행동해야 함
- 자식은 부모의 행동 규약을 준수해야 한다
- 부모 클래스의 속성과 메서드를 그대로 물려받으면 아무 문제 없다
대부분 오버라이딩 과정에서 문제가 발생
- 오버라이딩 과정에서 변수타입 변경, 메서드의 파라미터나 리턴값 변경
- 부모의 의도와 다르게 메서드를 변경하는 오버라이딩
LSP를 위반하면 OCP 붕괴(상속에는 닫혀 있고 개방에는 열려 있어야 하는 원칙)가 되어 OOP에 위협 발생
해결 방법
- 추상 클래스, 인터페이스를 활용
- 두 객체가 하는 일에 무언가를 추가해야 한다면 상속을 활용
- 하는 일이 같으면 하나의 클래스로 구성, 구분 필드를 추가
- 하는 일이 다르면 별개의 클래스로 구성
예시 설명
자동차를 기반 클래스로 하고, 스포츠카를 파생 클래스로 하는 상황을 가정해보자.
LSP를 위반한 코드 예시
class Car {
public void startEngine() {
// 엔진을 시동한다.
}
public void stopEngine() {
// 엔진을 멈춘다.
}
}
class SportsCar extends Car {
@Override
public void startEngine() {
// 고성능 엔진을 위한 추가 설정
super.startEngine(); // 기본 동작
// 스포츠카에만 적용되는 특별한 시동 절차
}
public void activateTurbo() {
// 터보 모드를 활성화한다.
}
}
이 경우, 모든 자동차가 터보 모드를 지원하지 않으므로 SportsCar 인스턴스를 Car 타입으로 사용할 때 `activateTurbo` 메소드에 대한 접근이 불가능해짐으로써 LSP를 위반한다.
LSP를 위반하지 않은 코드 예시
class Car {
public void startEngine() {
// 엔진을 시동한다.
}
public void stopEngine() {
// 엔진을 멈춘다.
}
}
class SportsCar extends Car {
@Override
public void startEngine() {
// 고성능 엔진을 위한 추가 설정
super.startEngine(); // 기본 동작
}
public void activateTurbo() {
// 터보 모드를 활성화한다.
}
}
interface Turbo {
void activateTurbo();
}
class EnhancedSportsCar extends SportsCar implements Turbo {
@Override
public void activateTurbo() {
// 터보 모드 구현
}
}
이 예시에서는 터보 기능을 지원하는 차량을 명시적으로 `Turbo` 인터페이스를 구현하게 함으로써, 모든 자동차(Car)가 기본적인 기능을 제공하고, 특정 기능(터보)을 필요로 하는 경우에는 해당 인터페이스를 통해 접근할 수 있도록 설계하여 LSP를 준수한다.
반응형
'개발 > 기타' 카테고리의 다른 글
SOLID 원칙 (3) - 개방 폐쇄 원칙(OCP) (0) | 2024.02.26 |
---|---|
SOLID 원칙 (2) - 단일 책임 원칙(SRP) (0) | 2024.02.22 |
SOLID 원칙 (1) - 들어가기 (0) | 2024.02.21 |
Let's Encrypt 작동 방식과 장점 및 단점 (0) | 2024.02.19 |
HTTPS와 SSL/TLS 암호 통신의 이해 (0) | 2024.02.16 |
댓글