디자인 패턴/GoF

[디자인 패턴] 생성 패턴 정리

로파이 2021. 2. 1. 00:03

예시 코드로 생성 패턴을 정리해보고 각 구현의 차이점을 알아보자.

 

추상 팩토리 Abstract Factory

  • 만들고자하는 인스턴스에 대한 정보가 담기거나 타입이 결정된 공장 객체를 통해 인스턴스를 생성하는 방법.
  • 주로 개별 객체를 만드는 것이 아니라 여러 객체가 합쳐진 시스템이나 환경을 만들기 위해 사용한다.
  • 그러한 전체 시스템 객체의 생성자나 Create()와 같은 연산으로 참조자를 반환하는 방법으로 객체를 생성하고
  • 매개변수로 추상 팩토리를 전달하면 원하는 시스템이 만들어진다.

팩토리 메서드 Factory Method

  • 생성자가 아닌 함수 호출로 인스턴스를 생성하는 방법으로 클래스 타입을 동적으로 설정할 수 있다는 장점이 있다.
  • 객체 생성시 가장 흔히 쓰이는 방법.
  • 매개변수로 생성하는 클래스 타입을 알아보기 위한 인자를 전달하거나 템플릿 방법을 쓸 수 있다.

 

추상 팩토리와 팩토리 메서드는 혼합해서 사용할 수 있는데 예시 코드에서 알아보도록 한다.

 

기본 클래스

 

Graphic 클래스 계통

  • Window 창에 그릴 수 있는 그래픽 요소를 정의하는 추상 클래스 Graphic과 그 밖에 파생클래스들을 나타낸다.
  • 기본적으로 원, 사각형, 이미지, 텍스트 객체만을 다루는 것으로 한다.

팩토리 메서드를 사용하는 추상 팩토리 GraphicFactory 클래스를 다음과 같이 구현할 수 있다.

class GraphicFactory
{
public:
	GraphicFactory();
	~GraphicFactory();
	// 팩토리 메서드를 이용
	virtual Rectangle* MakeRectangle(int w, int h);
	virtual Circle* MakeCircle(int radius);
	virtual Image* MakeImage(const char* file_name);
	virtual Text* MakeText(const char* text, int white_space);
};
// 예시
Image* GraphicFactory::MakeImage(const char* file_name)
{
	return new Image(file_name);
}

Text* GraphicFactory::MakeText(const char* text, int white_space)
{
	return new Text(text, white_space);
}
  • MakeXXX() 메서드는 팩토리 메서드로 연산을 통해 은닉된 방식으로 해당 인스턴스를 생성하여 참조자를 반환한다.

실제 사용자 코드에서는 다음과 같이 나타낼 수 있다.

Window* wnd = new Window(0, 0, 1280, 960);

// 팩토리 메서드 방식
GraphicFactory factory;
int draw_at_x = 200, draw_at_y = 400;
Graphic* rect = factory.MakeRectangle(200, 200);
rect->SetCenter(draw_at_x, draw_at_y);
rect->Draw(wnd);

Graphic* image = factory.MakeImage("test.jpg");
image->SetCenter(draw_at_x, draw_at_y);
image->Draw(wnd);

Graphic* text = factory.MakeText("안녕하세요.", 5);
text->SetCenter(draw_at_x, draw_at_y);
text->Draw(wnd);
  • 하지만 보통 개별 객체를 생성해서 사용하는 것을 외부에 나타나지 않으며 은닉된 하나의 시스템을 만드는 과정에 포함되어 사용된다. 

StandardWidget 클래스와 이를 찍어내는 WidgetGraphicFactory 클래스 추상 팩토리 패턴

  • StandardWidget이라는 클래스는 내부에 이미지와 텍스트로 이루어진 객체로 객체들을 모은 시스템이라고 할 수 있다.
  • 내부에서 객체들을 소유하고 Draw() 연산시 알맞게 Window 창에 띄우도록한다.
  • StandardWidget의 생성 방식은 생성자나 다른 CreateWidget()등으로 구현할 수 있는데, 간단하게 생성자에 factory 매개변수를 전달함으로 구현하였다.
  • Image와 Text의 구체 클래스 AlignedImage과 AlignedText를 사용하고 있다.
  • 추상 팩토리는 Widget을 생산할 수 있는 인터페이스 구현을 재정의하여 (GraphicFactory를 상속) 새로운 WidgetGraphicFactory 클래스를 정의할 수 있다.

WidgetGraphicFactory.h

위젯 생성을 위한 추상 팩토리의 구체 클래스로서 한 예시이다.

class WidgetGraphicFactory : public GraphicFactory
{
	int _mode;
public:
	WidgetGraphicFactory(int m);
	~WidgetGraphicFactory();
	virtual Image* MakeImage(const char* file_name) override;
	virtual Text* MakeText(const char* text, int white_space) override;
};

Image* WidgetGraphicFactory::MakeImage(const char* file_name)
{
	return new ImageAligned(file_name, _mode);
}

Text* WidgetGraphicFactory::MakeText(const char* text, int white_space)
{
	return new TextAligned(text, white_space, _mode);
}
  • WidgetGraphicFactory는 ImageAligned 객체와 TextAligned 객체를 생성하는 구체 추상 팩토리이다.

StandardWidget.h

#include "Image.h"
#include "Text.h"
#include "GraphicFactory.h"
#include <vector>
#include <string>

enum WIDGET_TYPE
{
	WIDE = 0, REASONABLE, NARROW
};

class StandardWidget
{
private:
	int sp;
	int width, height;
	int rows, cols;
	Graphic**_images;
	Graphic** _texts;
public:
	StandardWidget(WIDGET_TYPE widget,
					std::vector<std::string> &files, std::vector<std::string> &texts ,
					GraphicFactory& factory);
	~StandardWidget();
	int GetRows() const;
	int GetCols() const;
	void Insert(int at_row, int at_col, Graphic* src);
	void Delete(int at_row, int at_col);
	// rows, cols 에 맞는 이미지, 텍스트를 자동으로 Resize하여 화면에 띄움
	void Draw(Window*);
};
  • StandardWidget은 행과 열로 정렬된 Text와 Image를 띄우기 위해 설계된 클래스이다.
  • 생성자에서 GraphicFactory 추상 팩토리를 매개변수로 전달받고 있다.

- 실제 StandardWidget이 만들어지는 과정

#include "StandardWidget.h"

StandardWidget::StandardWidget(WIDGET_TYPE widget,
	std::vector<std::string>& files, std::vector<std::string>& texts,
	GraphicFactory& factory)
{
	// 위젯 설정
	switch (widget)
	{
		// 각 종류별 위젯 생성
		case WIDE:
		{

		}break;
		case REASONABLE:
		{

		}break;
		case NARROW:
		{

		}break;
	}

	_images = new Graphic * [files.size()];
	_texts = new Graphic * [texts.size()];

	// 추상 팩토리를 매개변수로 받아 자동 생성
	for (int f_id = 0; f_id < files.size(); f_id++)
	{
		// _images[f_id] = new Image(files[f_id].c_str());
		_images[f_id] = factory.MakeImage(files[f_id].c_str());
	}

	for (int f_id = 0; f_id < files.size(); f_id++)
	{
		// _texts[f_id] = new Text(texts[f_id].c_str(), sp)
		_texts[f_id] = factory.MakeText(texts[f_id].c_str(), sp);
	}
}

 

실제 사용자 코드에서는 StandardWidget인스턴스가 만들어지는 과정이 은닉화된다.

int main()
{
	// 추상 팩토리 방식으로 위젯을 생성
	vector<string> files, texts;
	WidgetGraphicFactory widgetfactory(ALIGN::CENTER);
	
	// 추상 팩토리 인스턴스를 전달한다.
	StandardWidget* widget = new StandardWidget(WIDGET_TYPE::WIDE, files, texts, widgetfactory);
	widget->Draw(wnd);
	return 0;
}
  • 매개변수로 widgetfactory가 전달된 것을 볼 수 있다.

프로토타입 (Prototype)

  • 객체를 직접 생성하지 않고 미리 생성된 인스턴스의 복제품을 사용하는 방식이다.
  • 사용자가 객체를 사용하기에 상태 변경이 크지 않은 방식으로 재사용 가능할 때 적용할 수 있다.
  • 프로토타입 관리자를 단일체 패턴으로 정의하고 프로토타입 원본 인스턴스들을 관리하게 한다.
  • Clone() 연산을 통해 원본과 똑같은 내용을 가진 새로운 인스턴스를 생성하여 반환하게 한다.

단일체 패턴 (Singleton)

  • 프로그램 전체에서 유일한 인스턴스임을 보장하고 객체 접근에 대한 인터페이스를 제공한다.
  • 사용자 코드에서 생성자를 호출하지 못 하도록 막고 접근 시 인스턴스화가 일어난다.

프로토타입 관리자를 단일체로 구현해보고 사용자 코드에서 예시를 알아본다.

 

ProtoTypeMangerSingle.h

class ProtoTypeManager
{
public:
	static ProtoTypeManager* Instance();
	Graphic* GetPrototype(const char*);
	void Init();
	void Release();

protected:
	ProtoTypeManager();

private:
	Circle* _p_circle = nullptr;
	Rectangle* _p_rect = nullptr;
	Text* _p_text = nullptr;
	static ProtoTypeManager* _instance;
};
  •  프로토타입 관리자는 단일체로 구현되어 있고 프로토타입의 원본을 관리하고 있다.
#include "ProtoTypeManager.h"

ProtoTypeManager* ProtoTypeManager::_instance = nullptr;

ProtoTypeManager::ProtoTypeManager()
{
}

ProtoTypeManager* ProtoTypeManager::Instance()
{
	if (_instance == 0)
	{
		_instance = new ProtoTypeManager;

		// 프로토타입 초기화
		_instance->Init();
	}
	return _instance;
}

void ProtoTypeManager::Release()
{
	if (_instance)
	{
		delete _instance;
		_instance = nullptr;
	}
}

Graphic* ProtoTypeManager::GetPrototype(const char* query)
{
	if (strcmp(query, "Circle"))
	{
		// 복사 생성자를 이용
		// return Circle(*this);
		return _p_circle->Clone();
	}
	// ... else

}

void ProtoTypeManager::Init()
{
	_p_circle = new Circle(5);
	_p_rect = new Rectangle(100, 100);
	_p_text = new Text("text", 10);
}

 

  • 원형관리자는 Init()을 통해 원형 인스턴스를 초기화하도록 한다.
  • GetPrototype() 함수를 통해 복사 생성하고자 하는 클래스를 문자열로 받아 복사 생성하도록 한다.

- 유연한 원형 패턴

class ProtoTypeManager
{
private:
	Circle* _p_circle;
	Rectangle* _p_rect;
	Text* _p_text;
public:
	ProtoTypeManager(Circle*, Rectangle*, Text*);
	Circle* MakeCircle() { return _p_circle->Clone(); }
	Rectangle* MakeRectangle() { return _p_rect->Clone(); }
};
  • 위처럼 사용하는 것이 바람직하고 생성자 인자를 통해 동적으로 구체적인 클래스 타입을 결정하도록한다.
  • 프로토타입 패턴은 상태를 변경하여 사용할 수 있으므로 Set 메서드를 지원하는 것이 좋다.

프로토타입 관리자를 이용하여 다음과 같이 복제 생성한 인스턴스를 다음과 같이 사용할 수 있다.