아래 코드를 함 보자.
#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 |