정사각형-직사각형 문제
- 앞에서 다이아몬드 문제에 의한 객체지향의 한계점을 살펴봤다면, 이번에는 정사각형-직사각형 문제로부터 객체지향의 한계점을 살펴보자
#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;
};
- 정사각형 is a 직사각형
- 네 변의 길이 모두
따라서 정사각형과 직사각형을 상속관계로 구현해보자
정사각형 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를 가리키는 것도 불가능하다
그래서 이 방법은 좋은 방법이 아님
아직까지도 정사각형-직사각형 문제를 깔끔하게 해결할 수 있는 방법이 나타나지 않았다.
'Programming Language > C++' 카테고리의 다른 글
[C++] 템플릿 특수화와 비타입 파라미터 (0) | 2024.06.28 |
---|---|
[C++] 함수 템플릿과 클래스 템플릿 (0) | 2024.06.28 |
[C++] 다중 상속과 다이아몬드 문제 (0) | 2024.06.28 |
[C++] 객체 지향 프로그래밍의 4대 원칙(원리) (0) | 2024.06.28 |
[C++] 상속에서의 형변환 - RTTI와 dynamic_cast (0) | 2024.06.28 |