리스코프 치환 원칙
- 타입 S가 타입 T의 서브타입이라면 프로그램의 속성 변경없이 T 타입의 객체를 S 타입으로 치환할 수 있어야한다.
- 즉 쉽게 말해 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다
요구사항
- 서브 타입에서 메소드 파라미터 타입은 반공변성(Contravariance)
- 서브 타입에서 메소드 return 타입의 공변성(Covariance)
- 서브 타입에서 메소드는 상위 클래스의 메소드에서 throw한 하위형을 제외하고 새로운 예외를 던질 수 없음
- 여기서 말하는 공변성과 반공변성이란?
- 공변성은 한 변수가 변하면 다른 변수도 변하는 성질을 말하며, 반공변성은 그 반대의 성질을 말한다. 위의 요구사항을 해석하면 리턴 타입 이나 예외의 경우 부모 객체가 의도하는 리턴타입을 자식이 마음대로 바꾸면 안된다는 것을 의미한다.
문제
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Getter @Setter
public class Rectangle {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public Number getArea() {
return height*width;
}
}
|
위와 같이 2개의 값을 받아 넓이를 구해주는 클래스가 있다고 하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class Square extends Rectangle{
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setHeight(height);
super.setWidth(height);
}
}
|
정사각형은 직사각형을 상속받아 값이 들어올 경우 정사각형이기때문에 밑변과 높이를 모두 똑같이 하도록 오버라이딩을 했다.
1
2
3
4
5
6
7
8
9
| public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setHeight(5);
System.out.println(rectangle.getArea());
}
}
|
위의 결과는 부모 클래스에 의도에 의하면 50이 나와야 하지만 위의 경우 25가 나온다. 이는 리스코프 치환 원칙을 위배하게 되는 것이다.
문제 해결
- 위의 경우를 통해 직사각형과 정사각형은 상속관계가 성립될거 같지만 성립되지 않는다. 그렇기 때문에 더 상위 클래스를 만들어 이들이 상속 받도록 하면된다.
1
2
3
| public interface Shape {
Number getArea();
}
|
위와 같이 Shape라는 상위 인터페이스를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
| public class Square implements Shape {
int length;
public Square(int length) {
this.length = length;
}
@Override
public Number getArea() {
return length*length;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class Rectangle implements Shape {
int width;
int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public Number getArea() {
return width*height;
}
}
|
이렇게 정사각형과 직사각형이 Shape 에게 상속을 받도록 구현한다.
1
2
3
4
5
6
7
8
9
| public class Main {
public static void main(String[] args) {
Shape rectangle = new Rectangle(5);
Shape square = new Square(10,5);
System.out.println(rectangle.getArea());
System.out.println(square.getArea());
} // 25 : 50
}
|
직사각형과 정사각형은 직접적인 상속관계가 없기 때문에 위에서 발생했던 리스코프 치환 원칙을 위반하지 않고 사용할 수 있다.