안녕하세요. 성조입니다.
이번 포스팅은 'SOLID'에서 L을 맡고 있는 리스코프 치환 원칙에 대해서 정리해 보려 합니다. SOLID를 공부하면서 5개 개념 중. L은 이해하는 데 가장 오래 걸렸던 파트라고 생각합니다.
혹여나 올바르지 못한 정보를 전달하고 있다면 언제든지 댓글 남겨주시면 감사드리겠습니다!
리스코프 치환 원칙(Liskov Substitution Principle, LSP)
리스코프 치환 원칙은(LSP) 상위 클래스의 인스턴스를 하위 클래스의 인스턴스가 파생(치환) 되어도 반드시 기본 타입 부분에서 완벽하게 대체하여 정확성을 유지해야 한다는 원칙이다.
즉, 상위(부모) 클래스의 객체와 하위(자식) 클래스는 같은 방식으로 동작되어야 한다는 의미이다. (오버라이딩)
LSP를 준수하는 과정에서 상위 클래스와 하위 클래스 간의 역할이 보다 명확하게 정의되면 유지보수와 가독성 향상을 도와줄 수 있으며, 객체 지향 프로그래밍에서 상속이라는 개념으로 코드 재사용을 가능하게 하는 메커니즘을 제공한다.
[리스코프 치환 원칙(LSP)이 적용되지 않은 예시]
class Rectangle {
protected int width, 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 int getArea() {
return width * height;
}
}
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);
}
}
public class Main {
public static void main(String[] args) {
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(4);
System.out.println(r.getArea()); // 예상 값: 20, 실제 값 : 16
}
}
Square(정사각형) 클래스는 Rectangle(직사각형) 클래스를 상속받았지만 특성을 유지하지 못했다.
직사각형과 정사각형은 구하는 방식도 다르다. 높이와 너비를 구하는 방법과 면을 통해서 구하는 공식이 다르기 때문이다. 그렇기 때문에 직사각형을 상속받은 정사각형은 특성이 귀속되지 않는다.
[리스코프 치환 원칙(LSP)이 적용된 예시]
class Shape {
public int getArea() {
return 0;
}
}
class Rectangle extends Shape {
protected int width, 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;
}
@Override
public int getArea() {
return width * height;
}
}
class Square extends Shape {
protected int side;
public void setSide(int side) {
this.side = side;
}
public int getSide() {
return side;
}
@Override
public int getArea() {
return side * side;
}
}
public class Main {
public static void main(String[] args) {
Shape s = new Square();
if (s instanceof Square) {
((Square) s).setSide(5);
} else if (s instanceof Rectangle) {
((Rectangle) s).setWidth(5);
((Rectangle) s).setHeight(4);
}
System.out.println(s.getArea()); // 정사각형은 20, 직사각형은 25의 값을 예측할 수 있다.
}
}
우선 리스코프 치환 원칙은 하위 클래스가 상위 클래스를 대체할 수 있어야 하는 원칙을 지켜야 한다.
이전 지켜지지 않은 않은 특성과 다르게 Shape(모양)이라는 클래스를 직사각형과 정사각형에 상속하여 각각 계산할 수 있도록 만든 코드이다. 이 코드는 정사각형인 경우 "변 * 변" 연산을 진행하고, 직사각형은 "너비 * 높이" 연산을 진행한다. 각각 올바르게 연산할 수 있고, Shape(모양)이라는 클래스에 각각의 값들이 상속되기 때문에 올바른 리스코프 치환 원칙을 달성했다고 정의할 수 있다.
리스코프 치환 원칙(Liskov Substitution Principle, LSP) 장점과 단점
[장점]
1. 코드의 일관성과 유지 보수성 향상
LSP를 준수하면 하위 클래스는 상위 클래스와 동일한 동작을 보장하는 장점이 존재한다.
상위 클래스를 하위 클래스가 오버라이드 하여 사용하기 때문에 인스턴스가 모두 양방향으로 호환되기 때문에 호환성이 좋고, 동일한 동작으로 일관성을 향상할 수 있다. 그리고 일괄된 동작을 하기 때문에 버그 수정이나 기능을 추가하는 경우 상위 클래스만 고려한 후. 상위 클래스에서 변경된 값을 하위 클래스로 내려주면서 개발하면 되고, 유지 보수 시간이 줄어들어서 유지 보수성이 향상된다.
2. 프로그램의 확장성 향상
LSP는 프로그램의 확장성을 증가시키는데 도움이 된다. 새로운 기능이 필요할 때 상위 클래스를 확장하여 새로운 하위 클래스를 만들 수 있게 된다. 이렇게 클래스의 구조나 기능을 크게 변경 없이 상위 클래스를 활용하여 하위 클래스를 조정한다.
3. 코드 재사용성 증가
하위 클래스는 상위 클래스에 있는 메서드를 오버라이드하여 사용하기 때문에 같은 기능을 두 번이나 구현할 필요 없이 코드를 재사용하면 된다.
[단점]
1. 오버라이딩 제한
리스코프 치환 원칙(LSP)은 상위 클래스의 인스턴스가 하위 클래스의 인스턴스로 치환될 때, 프로그램의 정확성이 변하지 않아야 한다는 원칙이 있다. 하위 클래스가 상위 클래스의 모든 속성과 메서드를 상속받아 사용하며, 상위 클래스의 기능을 확장하거나 수정하는 역할을 하기 때문이다.
예를 들면 상위 클래스에 '달리기'라는 메서드가 존재하고, 하위 클래스에서는 '달리기' 메서드를 오버라이드하여 사용하여 기본 동작으로 설정한다. 이때 '달리기'라는 기본 동작이 '점프'로 변경되면 안 되기 때문에 오버라이딩에 대한 제한이 걸린다. (변경 불가 이유는 오버라이딩에 대한 이해가 필요하다.)
2. 설계 복잡성 증가
상위 클래스와 하위 클래스 간의 관계를 신중하게 고려해야 한다. 이런 이유로 설계 복잡성이 증가할 수 있는데 만약, 하위 클래스가 상위 클래스의 동작을 완전히 따르지 않게 되는 경우 설계를 다시 해야 하기 때문이다.
3. 잘못된 상속 구조
LSP를 준수하기 위해 종종 강제로 상속을 사용해야 하는 경우가 있는데 상속은 객체지향 설계의 도구에 불과하기 때문에 항상 최상의 선택이라 단정 짓기 힘들다. 잘못된 상속 구조를 가지는 경우 불필요한 복잡성과 유지 보수의 어려움을 야기할 수 있는 단점이 존재한다.
오타나 궁금한 부분이 있다면 언제든지 댓글 남겨주시면 최대한 답변드리겠습니다.
다음 포스팅 때 뵙겠습니다. 감사합니다.
'Java ☕ > Java' 카테고리의 다른 글
[Java] 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) (0) | 2023.06.02 |
---|---|
[Java] 자바 SE, EE 정리 (with ME, FX 맛보기 정리) (0) | 2023.05.30 |
[Java] 개방-폐쇄 원칙 (Open-Closed Principle , OCP) (1) | 2023.05.30 |
[Java] 접근 지정자(Access Modifier) Private/Protected/Default/Public 정리 (0) | 2023.05.29 |
[Java] 단일 책임 원칙(Single Responsibility Principle, SRP) (0) | 2023.05.29 |