# SOLID
- SRP (Single Responsibility Principle) 단일 책임 원칙
- OCP (Open Closed Principle) 개방 폐쇄 원칙
- LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- DIP (Dependency Inversion Principle) 의존 역전 원칙
# 리스코프 치환원칙(Liskov Substitution Principle)
추상화와 다형성은 객체지향에서 가장 중요한 특성일 것이다. 그리고 상속을 사용하여 파생 클래스를 만들어 낼 수 있다. 그러나 상속에는 규율이 있을 것이다. 그러한 규율을 말해주는 것이 리스코프 치환 원칙이다. 결국 리스코프 치환원칙을 어긴다면 개방 폐쇄원칙(OCP)또한 어겨질 수 밖에 없다.
리스코프 치환원칙(LSP)의 정의는 매우 간단하다. 그러나 이해하기에는 손꼽히게 어렵다.
서브타입(subtype)은 그것의 기반 타입(base type)으로 치환 가능해야 한다.
Base Type과 Sub Type의 상속 관계에 있다. 이럴 경우 서브 타입은 상위 타입으로 치환이 가능해야하며 그럴때에도 기능상 문제가 없어야 한다는 것이다. 다형성의 아주 기본이 되는 원리이다.
BaseType sub = new SubType1();
BaseType sub = new SubType2();
BaseType sub = new SubType3();
우린 상속을 IS-A관계라고 흔히들 생각한다 A는 B이다 일 경우 상속관계로 표현하려고 한다. 그러나 이것은 리스코프 치환원칙의 위배되는 경우를 야기할 수 있다. 치환이 위배되는 간단한 경우를 생각해보자.
SubType은 BaseType과 치환이 가능해야 한다. 상속관계에서 업캐스팅을 한 것이기 때문에 SubType1()을 new 하는 것에서는 문제가 되지 않는다. 그러나, 이 foo함수에 이렇게 선언된 "sub"가 들어가 잘못된 동작을 할 경우 치환이 불가능 한 경우, LSP를 위반한 경우가 된다.
BaseType sub = new SubType1();
public void foo(BaseType object){
// do something
}
여기까지만 생각해본다면 별거 아닐 것이라고 생각 할 수 있지만 그 개념을 깨부술 수 있는 아주 미묘하고도 미묘한 정사각형 직사각형 문제를 통해서 LSP를 위반하는 경우를 살펴보자.
# 정사각형 - 직사각형
먼저 간단한 질문을 던져보겠다.
- 직사각형은 정사각형인가? -> False
- 정사각형은 직사각형인가? -> True
정사각형은 직사각형의 범주에도 포함된다. 이 예는 얼핏 보면 IS-A관계를 나타내며 상속의 관계로 표현 할 수 있을 것이다. 그러나 이것은 잘못 됐다. 잘못 된 이유는 뒤에서도 다시한번 설명하곘지만, IS-A관계는 객체가 아닌 그 행위로 정의해야 하는 것이다. 일단 이점을 알아두고 계속해서 정사각형-직사각형 예제를 살펴 보도록 하겠다.
일단 정사각형과 직사각형이 IS-A의 상속관계로 나타 낼 수 있다고 생각해보면, 아래와 같이 관계를 만들 수 있다.
## 정사각형은 높이와 너비가 필요 없다.
// 직사각형의 코드
public class Rectangle {
protected double width;
protected double height;
public double getWidth() {
return width;
}
public void setWidth(final double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(final double height) {
this.height = height;
}
public double getArea() {
return width * height;
}
}
직사각형의 넓이를 구하기 위해서는 높이와 너비를 곱해줘야 한다. 그러나 정사각형은 높이와 너비가 필요 없다. 한변의 길이가 4변의 길이와 같기 때문이다. 그렇기 때문에 정사각형에서는 getArea()가 호출되면 같은 길이가 곱해져야 한다. 일단 첫번째 문제가 발생했다! 이문제를 해결해보자
public class Square extends Rectangle {
@Override
public void setWidth(final double width) {
this.height = width;
this.width = width;
}
@Override
public void setHeight(final double height) {
this.height = height;
this.width = height;
}
}
Square에서 높이또는 너비가 바뀔 경우에, 임의로 두개를 동시에 조작하겠끔 한다. 그렇게 할 경우 높이와 너비가 동일한 값으로 적용 될 것이다. 이제 수학적으로 Square는 정확한 정사각형이라고 볼 수 있다.
만일 C++로 이 코드를 사용해서 작성했다면, 아래 f함수의 인자로 업캐스팅 된 Square를 넣어준다면, override된 메서드가 불리지 않고 Rectangle의 메서드가 불려 너비만 32로 바뀔 것이다. C++에서는 virtual함수로 지정을 해줘야 했기 때문이다.
void f(Rectangle & r) {
r.SetWidth(32);
}
물론 자바에서는 이런 virtual처리를 해줄 필요가 없다. 알아서 되기 때문이다. 자바에서는 문제가 발생하지 않을 것이다.
# IS-A는 행위에 의한 것이다. 사용자는 이런걸 고려하지 않는다.
결국 근본적인 문제는 여기서 일어난다. 사용자에게 Rectangle이라면 당연히 가로*세로의 길이를 예상할 것이다. 어찌 보면 당연 한 말이다.
public void test(Rectangle object){
object.setWidth(5);
object.setHieght(4);
assert(r.Area() == 20));
}
그러나 직사각형 객체가 들어 왔을때는 정상적으로 작동하겠지만, 정사각형 객체가 들어온다면, object는 4*4를 계산하여 "16 != 20"인 상태가 될 것이다. 즉 사용자는, Rectangle의 가로 길이를 바꾸는 것이 세로 길이를 바꾼다고 생각하지 않는다.
그래서 Square - Rectangle은 치환가능하지 않다. 이렇게 작성한 코드는 명백히 LSP를 위반할 것이다. 그리고 클라이언트가 의존하는 행위 관련이 있다.
이런 치환원칙을 어기면 코드가 오염될 것이다. 분기 코드가 점점 많아 질 것이다.
# 다른 예제
클린소프트웨어에는 Set 예제와 Line, LineSegment예제를 추가로 설명한다.
# 그래서?
결국 어떤 클래스 집합이 모두 같은 책임을 진다면, 공통 슈퍼클래스로 그책임을 상속 받을 수 있도록 설계해야한다. 그리고 그 슈퍼클래스는 추상클래스로써 구현체들을 구현 할 수 있게 하는 구조를 가져야하며, 치환원칙이 잘 성립해야한다.
기반 클래스와 파생클래스가 있을때, 파생 클래스는 기반 클래스의 동작은 모두 수행해야 한다. 그보다 덜한 동작을 할 경우 당연히 치환이 불가능하여 LSP를 위반 할 것이다.
기반 클래스에서 발생하지 않는 예외를 파생 클래스에서 발생되도 문제가 생긴다. 사용자는 기반 클래스의 범주에서 모든 행위를 생각하기 때문에, 이와 같은 예시가 파생클래스에서 생긴다면 치환 가능하지 않다. 사용자의 기대를 변화시키던, 파생 클래스가 그 예외를 발생시키지 않아야한다.
LSP는 OCP를 가능하게 해주는 원칙이다. LSP를 만족해야지, 기반타입으로 표현된 모듈을 수정하지 않고 확장 할 수 있다. IS-A용어를 일반적으로 생각하기에는 너무 넓은 범주가 있기 때문에, 치환 가능성에 염두하자.
'• 독서 > Design Pattern' 카테고리의 다른 글
[클린소프트웨어#8] SOLID-인터페이스 분리 원칙(Interface Segregation Principle) (0) | 2022.10.20 |
---|---|
[클린소프트웨어#7] SOLID-의존 관계 역전 원칙(Dependency Inversion Principle) (0) | 2022.10.20 |
[클린소프트웨어#5] SOLID-개방 폐쇄 원칙(Open Closed Principle) (0) | 2022.10.19 |
[클린소프트웨어#4] SOLID-단일 책임 원칙(Single Responsibility Principle) (0) | 2022.10.19 |
[클린소프트웨어#3] 애자일한(Agile)설계를 위한 방법과 7가지 부패한 특성 (1) | 2022.10.18 |