아래 코드를 함 보자.
#include <iostream>
#include <vector>
class not_implemented {}; // 예외 전용 클래스
class Shape
{
int color;
public:
virtual ~Shape() {}
void set_color(int c) { color = c; }
void draw()
{
std::cout << "grahics object init" << std::endl;
draw_imp();
std::cout << "grahics release" << std::endl;
}
protected:
virtual void draw_imp() = 0;
public:
virtual Shape* clone() { throw not_implemented(); }
virtual int get_area() { return -1; }
};
class Rect : public Shape
{
public:
void draw_imp() { std::cout << "draw rect" << std::endl; }
Shape* clone() { return new Rect(*this); }
};
class Circle : public Shape
{
public:
void draw_imp() { std::cout << "draw circle" << std::endl; }
Shape* clone() { return new Circle(*this); }
};
int main()
{
std::vector<Shape*> v;
while (1)
{
int cmd;
std::cin >> cmd;
if (cmd == 1) v.push_back(new Rect);
else if (cmd == 2) v.push_back(new Circle);
else if (cmd == 9)
{
for (auto p : v)
{
p->draw(); // 다형성. 새로운 도형이 추가되어도 수정될 필요 없다.
}
}
else if (cmd == 8)
{
std::cout << "몇번째 만든 도형을 복제 할까요 ?? >> ";
int k;
std::cin >> k;
v.push_back(v[k-1]->clone());// 다형성. 새로운 도형이 추가되어도 수정될 필요 없다.
}
}
}
우선 해당 코드가 어떤 코드인지 설명하면
Shape 라는 추상 클래스를 제공하고 , 이를 상속받는 Class ( Rect, Circle)을 만든다. Shape 추상 클래스는 -> 원하는 Shape을 복사하는 기능, color를 변경하는 기능, 면적을 구하는 기능을 제공하고자 하는데 모두 구현하지는 않았다. main 함수에서 while 반복문을 돌면서 cmd 값에 따라서 동작을 수행한다. cmd = 1 -> 새로운 Rect를 vector에 추가 cmd = 2 -> 새로운 Circle을 vector에 추가 cmd = 9 -> Vector에 쌓인 Shape들을 Draw cmd = 8 -> 유저에게 k값을 입력 받아서, k번째로 추가한 Shape을 복사 후 추가. |
여기서 얘기하고자 하는 부분이 몇 가지 있는데
- Draw
Draw 함수가 graphics object init -> Draw(그리는 과정) -> graphics release 한다고 가정하자.
만약에 이전에 다뤘던 Interface를 생각해보면 추상 클래스에 virtual void draw()를 정의 하고
파생 클래스에서 graphics object init -> Draw(그리는 과정) -> graphics release 를 재정의 해주면된다.
다만 이렇게 진행하면 Shape의 파생 클래스가 100개가 있다고 하면 graphics init과 release과정도 100번 적어줘야한다.
이런 부분이 비효율적이여서, 추상 클래스에서 Draw를 정의하고 , 실질적으로 그리는 과정만 virtual 함수로 다시 분리하여 해당 부분만 파생 클래스에서 재정의한다. 실질적으로 그리는 과정 => draw_imp 함수.
여기서 교훈이 "공통적으로 반복되는 부분과 아닌 부분을 구분해야한다" 인데,
공통적으로 반복되는 부분은 추상 클래스에서 정의하고, 아닌 부분만 딱 virtual로 파생 클래스에서 재정의 받는게
코드 줄을 줄이고 가독성 부분에서도 이점이 있다.
그래서 여기서 Rect와 Circle은 Draw 대신 draw_imp 함수만 재정의 해주면 된다. - draw_imp 함수는 왜 굳이 protected ??
지금 코드는 뭐 완벽하진 않지만, 실제로 Draw 하는 Code는 아주 main적인 Code일 것 입니다.
public으로 정의 한다면 외부에서 접근이 자유롭기 때문에 => 안정성이 떨어질 수 있습니다.
다만 파생 클래스에서는 재정의를 꼭 해줘야되기 때문에 protected로 정의하는게 좋습니다.
즉 , class의 내부 구현 사항은 캡슐화시키고 외부에서 직접적인 접근을 막기 위해서 protected를 사용합니다. - vector Container의 type?
std::vector<Shape*> v;
v.push_back(new Rect)
vpush_back(new Circle)
파생 클래스들을 Rect Circle 구분 없이 담기 위해서는 추상 클래스의 포인터를 type으로 지정해주면 편리합니다.
for(auto p : v) {p->Draw} 부분에서 p는 Shape pointer이지만 결국 draw 함수가 호출 되었을 때
RunTime에서 Rect 클래스의 draw_imp 함수, Circle 클래스의 draw_imp 함수를 혹은 clone 함수를 동적으로 Binding 해주기 때문에 딱히 문제가 없습니다.
'Programming > c++ Design Pattern' 카테고리의 다른 글
[C++ Design Pattern] Strategy Pattern (0) | 2023.11.29 |
---|---|
[c++ Design Pattern] Template Method (0) | 2023.11.29 |
[C++] 추상 클래스 (0) | 2023.11.29 |
[C++] 가상 함수 (1) | 2023.11.28 |
[c++] Upcasting (0) | 2023.11.28 |