본문 바로가기
Programming/c++ Design Pattern

[C++] OCP Code 예제

by 드가보자 2023. 11. 29.

아래 코드를 함 보자.

#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을 복사 후 추가. 

 

여기서 얘기하고자 하는 부분이 몇 가지 있는데

 

  1. 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 함수만 재정의 해주면 된다.

  2. draw_imp 함수는 왜 굳이 protected ??

    지금 코드는 뭐 완벽하진 않지만, 실제로 Draw 하는 Code는 아주 main적인 Code일 것 입니다.
    public으로 정의 한다면 외부에서 접근이 자유롭기 때문에 => 안정성이 떨어질 수 있습니다.
    다만 파생 클래스에서는 재정의를 꼭 해줘야되기 때문에 protected로 정의하는게 좋습니다. 
    즉 , class의 내부 구현 사항은 캡슐화시키고 외부에서 직접적인 접근을 막기 위해서 protected를 사용합니다.

  3. 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