티스토리 뷰

리스코프 치환 원칙(LSP)

리스코프 치환 원칙(LSP)

 

서브타입은 그것의 기반타입으로 치환가능해야한다.

타입 S의 객체 o1과 타입 T의 객체 o2가 있을 때, T로 프로그램 P를 정의했음에도 불구하고 o2를 o1으로 치환할 때 P의 행위가 변하지 않으면, S는 T의 서브타입이다.

 

리스코프 치환 원칙(LSP)는 개발자가 서브 클래스를 작성하기 위한 원칙을 제공합니다. 아래 LSP를 위반한 원칙들을 통해 자세히 알아봅시다.

Example 1.

image

 

DrawShape() 함수를 살펴보면 OCP를 위반하고 있다는 것을 알 수 있다. DrawShape()는 메서드에서 사용할 모든 Shape의 서브 타입에 대해 알고 있어야 하기 때문이다. 만일 Triangle 타입을 추가하고 싶다면 소스코드의 변경없이는 확장이 불가능하다.

이는 CircleSquareShape를 상속하기는 하지만 Draw()함수를 오버라이드하지 않아 발생한 문제이다. 즉, Shape의 서브 타입을 일일이 검사하여 각 클래스가 무엇인지 파악한 후 그에 맞는 함수를 호출한다.

 

Example 2.

image

 

 

정사각형과 직사각형은 is-a관계로 표현될 수 있다. 정사각형은 직사각형이기 때문이다. 그래서 Rectangle 클래스를 상속하여 Square 클래스를 만드는 것은 문제가 되지 않을 것이라고 생각이 된다.

 

문제점 1

상속을 하고 보니 Square클래스에서는 사용하지 않는 함수들이 존재함을 알 수 있다. 예를 들어 SetHeight()SetWidth()를 둘 다 사용할 필요는 없다. 즉 퇴화함수가 발생하는데 이는 잠정적인 LSP 위반이다. 일단 이 문제를 메서드 오버라이딩을 사용하여 해결했다고 치자.

void Square :: setWidth(double w){
	Rectangle :: SetWidth(w);
	Rectangle :: SetHeight(w);
}

 

문제점 2

이제 프로그래머는 상속의 장점중 하나인 다형성을 활용하기 위해 다음의 함수를 작성했다.

void f(Rectangle& r){
	r.setWidth(32);
}

setWidth()가 virtual로 선언이 되어있지 않아 함수가 정적 바인딩이 된 상태이다. 이 상태에서는 Rectangle :: setWidth() 가 호출이 되므로 다형성을 제대로 실현하지 못하게 된다. 결국 부모 클래스의 코드를 직접 수정하는 상황이 발생하므로 OCP를 위반하는 상황이다. 이 문제 또한 코드를 수정하여 해결했다고 치자.

 

문제점 3

이제 Rectangle 클래스를 클라이언트에서 사용을 해보자. 해당 클래스를 가져와 다음 함수를 작성했다고 해보자.

void g(Rectangle& r){
	r.setWidth(10);
	r.setHeight(5);
}

클라이언트는 r의 넓이가 50이 될 것을 기대하지만 이는 rSquare이라면 제대로 작동하지 않을 것이다. 왜 이러한 문제가 발생한걸까?

 

계약에 의한 설계

Design by Contract은 메소드의 사전조건과 사후조건을 선언하는 것으로 구체화된다. 사전조건과 사후조건에 대한 규칙은 다음과 같다.

  1. 하위형에서 선행조건은 강화될 수 없다.
  2. 하위형에서 후행조건은 약화될 수 없다.

위 사례에서는 후행조건이 약화되었다고 볼 수 있다. Rectangle :: setWidth(input)에서의 후행조건은 itsWidth==input && itsHeight==prevHeight 이지만 Square :: setWidth(input)에서는 itsHeight==prevHeight의 조건을 충족하지못하므로 후행조건이 약화되었다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 31
글 보관함