Programming Language/C++

[C++] 정사각형-직사각형 문제

lumana 2024. 6. 28. 18:17

정사각형-직사각형 문제

  • 앞에서 다이아몬드 문제에 의한 객체지향의 한계점을 살펴봤다면, 이번에는 정사각형-직사각형 문제로부터 객체지향의 한계점을 살펴보자
#include <iostream>
using namespace std;

class Rectangle {  // 직사각형
   public:
    Rectangle(double a, double b) : a(a), b(b) {}

   private:
    double a, b;
};

class Square {  // 정사각형
   public:
    Square(double a) : a(a) {}

   private:
    double a;
};
  1. 정사각형 is a 직사각형
  2. 네 변의 길이 모두
  • 따라서 정사각형과 직사각형을 상속관계로 구현해보자

  • 정사각형 is a 직사각형 이므로, 정사각형은 직사각형으로 부터 상속을 받아야 한다

    • 이 때 정사각형은 멤버 1개만 필요한데, 직사각형으로 부터 멤버 2개를 받아오게 된다
#include <iostream>
using namespace std;

class Rectangle {  // 직사각형
   public:
    Rectangle(double a, double b) : a(a), b(b) {}

   private:
    double a, b;
};

class Square : public Rectangle {  // 정사각형
   public:
    Square(double a) : Rectangle(a, a) {}
};

int main() {
    Square s(10);
}
  • 가로, 세로에 각각 a를 넣어주면 원하는 대로 동작하기는 한다

    • 정사각형이 직사각형으로부터 상속을 받으면서, sizeof(Square)가 2배가 되버리는 단점이 존재한다
#include <iostream>
using namespace std;

class Rectangle {  // 직사각형
   public:
    Rectangle(double a, double b) : a(a), b(b) {}
    void ResizeX(double k) { a *= k; }
    void ResizeY(double k) { b *= k; }

   private:
    double a, b;
};

class Square : public Rectangle {  // 정사각형
   public:
    Square(double a) : Rectangle(a, a) {}
};

int main() {
    Square s(10);
        s.ResizeX(2); // 이러면 s는 더 이상 정사각형이 아니게 된다(20, 10)
}
  • 만약 Rectangle에 Resize 함수가 존재한다고 하면

    • Square 인스턴스의 ResizeX()를 호출하면 s는 더 이상 정사각형이 아니게 된다
  • C++에서는 상속 받은 메서드를 삭제할 수 있다.

class Square : public Rectangle {  // 정사각형
   public:
    Square(double a) : Rectangle(a, a) {}
    void ResizeX(double k) = delete;
    void ResizeY(double k) = delete;
    void ResizeXY(double k) {
        Rectangle::ResizeX(k);
        Rectangle::ResizeY(k);
    }
};
  • 이렇게 하면 Square에서는 ResizeX와 ResizeY를 호출할 수 없을 것 같지만, 한 가지 함정이 더 있다.

  • 아래 예시에서는 Rectangle을 다형적 클래스로 선언한다.

#include <iostream>
using namespace std;

class Rectangle {  // 직사각형
   public:
    Rectangle(double a, double b) : a(a), b(b) {}
    virtual ~Rectangle() {}
    void ResizeX(double k) { a *= k; }
    void ResizeY(double k) { b *= k; }

   private:
    double a, b;
};

class Square : public Rectangle {  // 정사각형
   public:
    Square(double a) : Rectangle(a, a) {}
    void ResizeX(double k) = delete;
    void ResizeY(double k) = delete;
    void ResizeXY(double k) {
        Rectangle::ResizeX(k);
        Rectangle::ResizeY(k);
    }
};

int main() {
    Rectangle *r = new Square(10);
    r->ResizeX(2);
}
  • r->ResizeX(2); 를 막을 수 없는 문제가 발생한다

  • 또 다른 해결책 : immutable(불변) 클래스

    • ResizeX(), ResizeY()를 실행하면 Square 멤버 변수의 값 자체가 바뀌게 된다

    • 불변 클래스는 모든 멤버 변수가 const로 선언된 클래스이다.

#include <iostream>
using namespace std;

// 불변 클래스
class Rectangle {  // 직사각형
   public:
    Rectangle(double a, double b) : a(a), b(b) {}
    virtual ~Rectangle() {}
    Rectangle ResizeX(double k) const { return Rectangle(a * k, b); }
    Rectangle ResizeY(double k) const { return Rectangle(a, b * k); }
    double GetA() const { return a; }
    double GetB() const { return b; }

   private:
    const double a, b;
};

// 얘도 불편 클래스가 됨
class Square : public Rectangle {  // 정사각형
   public:
    Square(double a) : Rectangle(a, a) {}
};

int main() {
    Square s(10);
    Rectangle r = s.ResizeX(2);
    cout << s.GetA() << endl;
    cout << s.GetB() << endl;
    cout << r.GetA() << endl;
    cout << r.GetB() << endl;
}
  • 실행결과
10
10
20
10
  • s.ResizeX()는 s를 변경시키지 않는다

  • 불변 클래스를 사용하면 정사각형-직사각형 문제를 간단하게 해결할 수 있다.

  • 물론 아직 불필요한 멤버를 갖는 문제를 해결하지 못했다.

    • 만약 Square를 부모 클래스로 설계한다면?
#include <iostream>
using namespace std;

class Square {  // 정사각형
   public:
    Square(double a) : a(a) {}

   private:
    double a;
};

class Rectangle : public Square {  // 직사각형
   public:
    Rectangle(double a, double b) : Square(a), b(b) {}

   private:
    double b;
};

int main() {
    // 불가능 Rectangle *r = new Square(10);
}
  • 문제점

    • 멤버 변수의 관점에서 깔끔해보일 수 있으나, 상식에 어긋나고 코드를 보는 사람도 헷갈리게 된다

    • Rectangle을 통해서 Square를 가리키는 것도 불가능하다

  • 그래서 이 방법은 좋은 방법이 아님

  • 아직까지도 정사각형-직사각형 문제를 깔끔하게 해결할 수 있는 방법이 나타나지 않았다.