티스토리 뷰
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
무슨 말인지 모르겠다. 예제 코드로 살펴보자.
리스코프가 잘 지켜지지 않은 사례 : 직사각형-정사각형 문제
public class Rectangle{
private int width;
private int height;
public void setWidth(int width){
this.width = width;
}
public void setHeight(int height){
this.height = height;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
}
public class Square extends Rectangle{
@Override
public void setWidth(int width){
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height){
super.setWidth(height);
super.setHeight(height);
}
}
정사각형은 직사각형이니, 직사각형을 상속해서 정사각형 클래스를 만들었다. 정사각형은 너비와 높이가 같으므로, 너비와 높이가 항상 같도록 setter를 override 했다.
이제 다음과 같이 rectangle 클래스를 사용하는 코드가 있다고 가정하자.
public void increaseHeight(Rectangle rec){
if(rec.getHeight() <= rec.getWidth()){
rec.setHeight(rec.getWidth() + 10);
}
}
높이가 너비보다 10 만큼 크도록 만드는 코드이다.
그런데, 만약에 increaseHeight() 에 Square 객체가 전달된다면 어떻게 될까?
Square 객체의 setHeight는 높이와 너비를 모두 같은 값으로 설정하기 때문에, 위 코드가 제 기능을 하지 못할 것이다.
public void SomeClient(){
Square square = new Square();
square.setWidth(10);
XXX.increaseHeight(squ);
square.getWidth(); //10을 기대하지만, 20
square.getHeight(); //20
}
상위 타입의 객체(Rectangle)를 하위 타입의 객체(Square)로 치환했을 때, 원 프로그램(SomeClient)가 정상적으로 동작하지 않으므로, 이는 LSP를 위반한 사례이다.
이 문제를 해소하기 위해, instanceof를 사용할 수 있을 것이다.
public void increaseHeight(Rectangle rec){
if(rec instanceof Square)
throw new CantSupportSquareException();
if(rec.getHeight() <= rec.getWidth()){
rec.setHeight(rec.getWidth() + 10);
}
}
OCP에서 살펴본 것처럼, 이렇게 instanceof로 분기처리를 하는 것은 좋은 객체지향 설계 원칙에 어긋난다.
그러면 왜 이런 문제가 생겼고, 어떻게 이를 해결할 수 있을까?
- 위 문제는 사실 직사각형-정사각형이 상속 관계로 구현하기에 적절치 않기 때문에 발생한 문제다. 기하학적으로는 정사각형이 직사각형의 일부이므로 이를 상속받는게 자연스러워 보이지만, 프로그램 상에서는 아니다.
따라서 Square 클래스를, Rectangle로부터 상속받지 않고 따로 구현하는 것이 해결 방법이다.
LSP는 계약과 확장에 대한 것
1) 계약
직사각형-정사각형 문제를 객체의 계약 측면에서 볼 수도 있다.
setHeight() 메소드는 Rectangle에서 높이값을 설정한다. 하지만 하위 클래스인 Square에서 너비값까지 변경해서 LSP를 위반하게 된 것이다.
이를 방지하기 위해서, 하위 타입은 상위 타입에서 정의한 명세를 벗어나지 않는 범위에서 구현해야 한다.
2) 확장
LSP는 OCP와도 연관이 있다. LSP를 위반하면 OCP를 어길 가능성도 높아진다.
간단한 예시를 살펴보자.
public class Coupon{
public int calculateDiscountAmount(Item item){
return item.getPrice() * discountRate;
}
}
calculateDiscountAmount() 메소드는 Item 을 받아서 할인 금액을 알려준다.
이때 요구사항이 변경되어서 특정 item에는 할인을 적용하지 않는다고 한다. 그래서 Item을 상속받는 SpecialItem 클래스를 구현했다. 그러면 Coupon 클래스는 다음과 같이 바뀔 것이다.
public class Coupon{
public int calculateDiscountAmount(Item item){
if(item instanceof SpecialItem){ //LSP 위반
return 0;
}
return item.getPrice() * discountRate;
}
}
Item의 하위 타입(SpecialItem)을 Coupon에 넘겨주자 원래 있던 기능이 잘 동작하지 못하게 되었고, 이를 해결하기 위해 instanceof로 예외처리를 해준 모습이다.
이는 앞선 예시에서 살펴본 바와 같이 LSP를 위반한 것이며, 이를 잘못된 방식으로 해결한 예이다. 만약 아이템과 관련된 정책이 늘어난다면, Coupon 역시 계속해서 수정해주어야 하며, OCP에도 어긋나게 된다.
그렇다면 이를 바로잡으려면 어떻게 해야할까?
Item을 더 정교하게 추상화하면 된다.
public class Item {
public boolean isDiscountAvailable(){
return true;
}
...
}
public class SpecialItem extends Item{
@Override
public boolean isDiscountAvailable(){
return false;
}
}
할인이 가능한지 여부를 판단하는 기능을 Item에 포함시켰다.
public class Coupon{
public int calculateDiscountAmount(Item item){
/*
if(item instanceof SpecialItem){ //LSP 위반
return 0;
}
*/
if(!item.isDiscountAvailable())
return 0;
return item.getPrice() * discountRate;
}
}
이제 Coupon 클래스에 Item이 오든, SpecialItem이 오든, 그밖에 다른 하위 타입의 클래스가 오든 Coupon 클래스는 수정하지 않아도 된다.
LSP를 지키지 않으면 OCP도 위반하게 되는 사례를 살펴보았다.
LSP를 지켜야 OCP를 지킬 수 있고, 기능의 확장이 용이해진다.
코드 출처, 참고 도서 : 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴, 최범균 지음, 인투북스
'OOP' 카테고리의 다른 글
예시로 이해하는 의존 역전 원칙(DIP) (0) | 2021.06.04 |
---|---|
예시로 이해하는 인터페이스 분리 원칙(ISP) (0) | 2021.06.04 |
예시로 이해하는 개방 폐쇄 원칙(OCP) (0) | 2021.06.02 |
예시로 이해하는 단일 책임 원칙(SRP) (0) | 2021.06.02 |
좋은 객체지향 설계의 5가지 원칙(SOLID) (0) | 2021.01.14 |
- Total
- Today
- Yesterday
- gracefulshutdown
- ec2
- 서비스추상화
- 스프링
- 객체지향
- 김영한
- 프로그래머스
- 카카오
- 백기선
- 메서드레퍼런스
- 템플릿콜백
- 디자인패턴
- BOJ
- 데코레이터패턴
- 예외처리
- 토비
- 코테
- 프록시패턴
- c++
- 프록시
- java
- SOLID
- provider
- 토비의스프링
- AOP
- 자바
- 토비의봄TV
- 코딩테스트
- OOP
- 자바스터디
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |