넌왜C++을C처럼쓰냐?(OOP)

[OOP 2] The Liskov Substitution Principle

케이피 2008. 5. 25. 21:16

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
 
  기반 클래스의 인터페이스를 통해 객체를 사용할때, 사용자는
  기반 클래스의 전상태와, 후상태만 알면 됀다.
 
  ... 나머지는 너무 어렵다... ㅠ_ㅠ