[OOP 2] The Liskov Substitution Principle
OOP 법칙 2
# The Liskov Substitution Principle (LSP)
* FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES
MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT.
<기본클래스의 포인터나 레퍼런스를 쓰는 함수는 유도클래스의 객체를
알지 못한채 사용할 수 있어야한다>
- 기본 클래스에서 포인터나 레퍼런스를 쓰는 함수에서 유도된 클래스의 객체를
알아야만 한다면 OCP(Open-Closed Principle) 에 어긋난것이다.
후에 새 클래스가 추가되면 어차피 코드에 수정이 이루어지기 때문이다.
A Simple Example of a Violation of LSP
- 가장 빛나는(?) LSP 의 위반중 하나는 클래스의 선택하기 위해
RTTI(Run-Time Type Information)를 사용하는 것!
void DrawShape(const Shape& s){
if( typeid(s) == typeid(Square) )
DrawSquare(static_cast<Square&>(s));
else if( typeid(s) == typeid(Circle) )
DrawCircle(static_case<Circle&>(s));
}
- 이런식의 함수는 OOD의 저주야~~~ ㅠ_ㅠ
Square and Rectangle, a More Subtle Violation.
class Rectangle{
public:
void SetWidth(double w){ itsWidth=w; }
void SetHeight(double h) { itsHeight=h; }
double GetWidth() const { return itsWidth; }
double GetHeight() const { return itsHeight; }
private:
double itsWidth;
double itsHeight;
};
C++에서는 종종 상속은 ISA relationship 이라고한다.
다른말로 새로운 객체가 ISA relationship으로 예전 객체와 실행할수있다
말할수 있으면 새로운 객체는 예전 객체를 상속 받은것이다.
Square 를 Rectangle 로 상속 받아 만들 때 문제
Square 는 itsHeight와 itsWidth가 필요없다는거지~
Square 는 SetWidth(), SetHeight()가 완전 어울리지 않는다는거지~
Square 는 Height와 Width가 동일해!
낭비야~
void Square::SetWidth(double w){
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double h){
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
이런식으로 바꾼더라도...
Square s;
s.SetWidth(1);
s.SetHeight(2); // 여기까진 좋은데...
void f(Rectangle& r){
r.SetWidth(32); // Calls Rectangle::SetWidth()
}
Square의 Width만 바뀌고 Height는 바뀌지 않아~ (LSP 위반!)
실패의 원인은 SetWidth(), SetHeight()가 virtual가 아니기 때문이쥐~
나름 고친 코드
class Rectangle{
public:
virtual void SetWidth(double w) { itsWidth=w; }
virtual void SetHeight(double h) { itsHeight=h; }
double GetHeight() const { return itsHeight; }
double GetWidth() const { return itsWidth; }
private:
double itsHeight;
double itsWidth;
};
class Square : public Rectangle{
public:
virtual void SetWidth(double w);
virtual void SetHeight(double h);
};
void Square::SetWidth(double w){
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double h){
Rectangle::SetHeight(h);
Rectangle::SetWidth(h);
}
The Real Problem
하튼간에 기반 클래스의 인터페이스를 사용시 유도 클래스에서 기대한 결과가 나와야됀다는거야
기반 클래스가 뭔지 전혀 몰라도 돼게~
결론은 위의 클래스가 디자인이 잘못됬다는거겠지...
대충 마무리 결론!
LSP(Liskov Substitution Principle)
- 기반 타입은 서브 타입으로 대체할 수 있어야 한다.
- 자식 타입들은 부모 타입들이 사용되는 곳에 대체될 수 있어야 한다.
Design by Contract
기반 클래스의 인터페이스를 통해 객체를 사용할때, 사용자는
기반 클래스의 전상태와, 후상태만 알면 됀다.
... 나머지는 너무 어렵다... ㅠ_ㅠ