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

Posted by 케이피

OOP 법칙 1

# The Open-Closed Principle

 *OOD (Object Oriented Design)
 "Software entities(classes, modules, functions, Etc.)
 should be OPEN for EXTENSION, but CLOSED for MODIFICATION"
 <확장만 하고 수정하지 말라구!>
 
 *It says that you should design modules that never change.
  when requirements change, you extend the behavior of such modules by adding new code,
  not by changing old code that already works.
  <모듈을 바꾸지말고 필요하면 새 코드를 추가해 기능을 확장시키고
   이미 작업한 예전 코드 바꾸지마!>

1. They are "Open For Extension"

2. They are "Closed For Modification"

 Abstraction is the Key.
  * OCP를 따르지 않은 코드(절차적해결법)는 if나 switch로...
  List 1 - Procedural Solution to the Square/Circle Problem.
  enum Shape{
   ShapeType itsType;
  };
 
  struct Circle{
   ShapeType itsType;
   double itsRadius;
   Point itsCenter;
  };
 
  struct Square{
   ShapeType itsType;
   double itsSide;
   Point itsTopLeft;
  };
 
  void DrawSquare(struct Square*);
  void DrawCircle(struct Circle*);
 
  typedef struct Shape *ShapePointer;
 
  void DrawAllShapes(ShapePointer list[], int n){
   int i;
   for(i=0; i<n; ++i){
    struct Shape* s = list[i];
    switch(s->itsType){
     case square:
      DrawSquare((struct Square*)s);
      break;
     
     case circle:
      DrawCircle((struct Circle*)s);
    }
   }
  }
 
  * OCP를 따르는 코드(OOD 해결법)는 객체만 추가하면...
  List 2 - OOD solution to Square/Circle problem.
  class Shape{
   public:
    virtual void Draw() const = 0;
  };
 
  class Square : public Shape{
   public:
    virtual void Draw() const;
  };
 
  class Circle : public Shape{
   public:
    virtual void Draw() const;
  };
 
  void DrawAllShapes(Set<Shape*>& list){
   for(Iterator<Shape*>i(list); i; ++i)
    (*i)->Draw();
  }
 
  DrawAllShapes 동작을 확장할 때 DrawAllShapes 함수를 고칠 필요없이
  Shape 를 상속받은 새로운 Class만 만들면 된다는 이야기!
 
 
 Strategic Closure
 - 100% 닫혀있는 프로그램은 없다.
  = 위의 DrawAllShapes 함수도 만약 Square를 그리기전에 Circle을 그리도록 해야한다면
   결국 바꿔야한다.
 - 결국 프로그램 디자이너가 모든 변화 가능성에 대비한 디자인을 해야한다!
 
  1) Using Abstraction to Gain Explicit Closure
 
  * 도형 그리는 순서는 조정하였지만 OCP에 맞지 않는 코드
  List 3 - Shape with ordering methods.
  class Shape{
   public:
    virtual void Draw() const = 0;
    virtual bool Precedes(const Shape&) const = 0;
   
    bool operator<(const Shape& s) { return Precedes(s); }
  };
 
  List 4 - DrawAllShapes with Ordering
  void DrawAllShapes(Set<Shape*>& list){
   // Copy elements into OrderedSet and then sort.
   OrderedSet<Shape*> orderList = list;
   orderList.Sort();
   
   for( Iterator<Shape*> i(orderedList); i; ++i )
    (*i)->Draw();
  }
 
  List 5 Ordering a Circle
  bool Circle::Precedes(const Shape& s) const{
   if( dynamic_case<Square*>(s) )
    return true;
   else
    return false;
  }
 
 2) Using a "Data Driven" Approach to Achieve Closure.
 
  List 6 - Table driven type ordering mechanism
  #include <typeinfo.h>
  #include <string.h>
  enum { false, ture };
  typedef int bool;
 
  class Shape{
   public:
    virtual void Draw() const = 0;
    virtual bool Precedes(const Shape&) const;
   
    bool operator<(const Shape& s) const { return Precedes(s); }
   
   private:
    static char* typeOrderTable[];
  };
 
  char* Shape::typeOrderTable[] = { "Circle", Square", 0 };
 
  // This function searches a table for the class names.
  // the table defines the order in which the
  // shapes are to be drawn. Shapes that are not
  // found always precede shapes that are found.
  bool Shape::Precedes(const Shape& s) const{
   const char* thisType = typeid(*this).name();
   const char* argType = typeid(s).name();
   bool done = false;
   int thisOrd = -1;
   int argOrd = -1;
   for( int i=0; !done; ++i ){
    const char* tableEntry = typeOrderTable[i];
   
    if( tableEntry != 0 ){
     if( strcmp(tableEntry, thisType) == 0 )
      thisOrd = i;
     if( strcmp(tableEntry, argType) == 0 )
      argOrd = i;
     
     if( (argOrd > 0) && (thisOrd > 0) )
      done = true;
    }
    else // table entry == 0
     done = true;
   }
   return thisOrd < argOrd;
  }
 
  3) Extending Closure Even Further.
 
Heuristics and Conventions
 1) Make all Member Variables Private.
 OOD 의 오래된 관습이랄까?
 
 2) No Global Variables -- Ever.
 
 3) RTTI is Dangerous. -- 쓰지마.
 
Conclusion
 OCP을 잘지켜야된다. 재사용성, 유지보수성을 위해서...

Posted by 케이피