추상 팩토리 패턴
서로 연관된 또는 의존적인 객체들로 이루어진 제품군을 생성하기 위한
인터페이스를 제공한다. 구상 클래스는 서브 클래스에 의해 만들어진다.
추상 팩토리 패턴 예제
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace::std;
class CAudio
{
public:
virtual void Run() = 0;
};
class CWindowAudio : public CAudio
{
public:
virtual void Run()
{
cout << "Window Audio Run" << endl;
}
};
class CUnixAudio : public CAudio
{
public:
virtual void Run()
{
cout << "Unix Audio Run" << endl;
}
};
class CVideo
{
public:
virtual void Run() = 0;
};
class CWindowVideo : public CVideo
{
public:
virtual void Run()
{
cout << "Window Video Run" << endl;
}
};
class CUnixVideo : public CVideo
{
public:
virtual void Run()
{
cout << "Unix Video Run" << endl;
}
};
class COSFactory
{
public:
virtual CAudio* CreateAudio() = 0;
virtual CVideo* CreateVideo() = 0;
};
class CWindowFactory : public COSFactory
{
public:
virtual CAudio* CreateAudio(){ return new CWindowAudio; }
virtual CVideo* CreateVideo(){ return new CWindowVideo; }
};
class CUnixFactory : public COSFactory
{
public:
virtual CAudio* CreateAudio(){ return new CUnixAudio; }
virtual CVideo* CreateVideo(){ return new CUnixVideo; }
};
void main()
{
COSFactory* pOSFactory = new CWindowFactory;
CVideo* pVideo = pOSFactory->CreateVideo();
CAudio* pAudio = pOSFactory->CreateAudio();
pVideo->Run();
pAudio->Run();
delete pVideo;
delete pAudio;
delete pOSFactory;
}
==========================================================================================================
암튼 읽어주시는 분들께는 감솨를 드립니다. ㅠ_ㅠ
그리고 깜빡하고 말씀 안드린 것이 있는데, 디자인 패턴은 언어에 독립적입니다. 여기서 C++을 써서 구현을 했지만 다른 모든 언어에서 구현이 가능하다고 합니다. 심지어 기존의 C로도 구현이 가능하다고 합니다. 저는 C++밖에 모르기 때문에 '어떻게 다른 언어로 구현하느냐'라고 묻지는 마세요.
< Abstract Factory >
<의도>
<동기>
(... 이것이 게임회사의 현실입니다.. -_-;; )
당신은 이제 각기 다른 건물 구성을 가진 세 종족의 인터페이스를 통일시키지 않으면 빌의 배치기에 목숨을 잃을 판입니다.
이것에 대한 좋은 해결책이 이 패턴입니다. 이 패턴은 객체들을 생성하는 것과 사용하는 것을 분리시켜서 그 객체의 내용(구현)에 신경을 쓰지 않고 객체를 사용할 수 있도록 해줍니다. 결국에 이것은 인터페이스와 구현을 분리시키게 되는 것입니다.
이제 빌의 배치기에 위협당하는 상황으로 돌아가봅시다. 당신이 어쩔 수 없는 상황에 부처님께 기도를 드리자 부처님이 당신의 꿈에 나타나 계시를 내려 주십니다. "너의 정성이 갸륵하여 인터페이스 클래스라는 것을 알려 주겠노라!!" 할렐루야~~
class cUnit
virtual void Special1() {} //재정의 안해도 됨
class cZealot : public cUnit
이것이 인터페이스 클래스입니다.. 먼저 순수 가상함수를 눈여겨 보세요
virtual cUnit * Create1stUnit() = 0;
이것이 순수가상함수입니다.. 뒤에 = 0; 이 붙이있는 것을 보고 의아하게 생각 하시겠지만 이것은 0을 대입한다는 뜻이 아니라 구현이 없다는 뜻입니다. 이 순수 가상함수가 하나라도 있는 클래스는 인스턴스를 생성할 수 없고 순수 가상 함수를 포함한 클래스를 상속한 클래스는 순수 가상 함수들을 모두 재정의 해야만 인스턴스를 생성할 수 있습니다. 저 늠름한 질럿 클래스를 보시면 Attack함수부터 HoldPosition까지 4개 모두를 재정의한 것을 알 수 있습니다. 때문에 Zealot 클래스는 인스턴스를 생성할 수 있지만 cUnit은 인스턴스를 생성할 수 없습니다. 만약 cZealot에서 4개의 순수 가상 함수 중 하나라도 빼먹고 재정의 하지 않았다면 컴파일러가 에러를 일으키기 때문에 cZealot의 인스턴스도 생성할 수 없게 됩니다.. 순수 가상 함수는 실제 함수가 아닌 인터페이스에 대한 약속이라고 보면 됩니다. 순수 가상 함수에 대해 설명을 하다 보면 끝이 없으니 C++ 비급을 보고 열심히 연마하시길 바랍니다.
또 알아햐 할 것은 다형성입니다. 설명을 위해 하이템플러를 추가하겠습니다.
class cHiTemplar : public cUnit
cZealot zealot;
cUnit * Unit1 = &zealot;
Unit1 = &templar;
이것이 다형성입니다. Unit1에 들어간 클래스형에 따라 그 클래스형에 맞는 함수가 호출되는 것을 알 수 있을 것입니다. 이것 역시 자세한 것은 C++비급을 참고하세요.
이 인터페이스 클래스는 인터페이스와 구현을 확실히 분리시켜 주기 때문에 유닛을 사용에 대한 프로그래밍 할 때 유닛이 무엇인지 신경 쓸 필요도 없게 해줍니다. 또한 이것으로 인해 모든 유닛들을 큐나 리스트, 트리 등의 자료구조로 다룰 수 있게 되는 이점도 있습니다.
또 다른 이점은 사용할 때에는 인터페이스 클래스만 include 해주면 되기 때문에 컴파일 연관성을 줄여주고 컴파일을 빠르게 해준다는 것입니다.
class cPrimaryFactory //1차 생산건물에 대한
//베이스 클래스의 소멸자는 virtual이어야 합니다.
class cBarrack : public cPrimaryFactory
};
여기서 CreateUnit함수들의 구현을 단순히 [return new 유닛] 이렇게 해놨는데, 실제로는 이렇게 단순하지 않을 것입니다. 메모리 유출을 피하기 위해 먼저 Object Manager같은 클래스에 포인터를 저장하고 난 다음에 리턴하던지 하겠죠.
class cGateway : public cPrimaryFactory
class cHatchery : public cPrimaryFactory
저그의 해처리는 특이한 구조이기 때문에 다른 패턴을 동원해야 합니다만 설명을 위해 단순화하기로 하겠습니다. UML도표를 그려서 설명하면 쉬운데.. 힘들군요. 뭐~ 이 강좌는 어디까지나 소개 차원에서 하는 것이므로 실제로 이 패턴들을 써서 프로그래밍을 하고 싶으신 분은 디자인 패턴 책을 반드시 사시길 바랍니다. 이미 패턴을 모두 알고 있다 해도 디자인 패턴 책은 패턴 사전으로서의 가치가 크기 때문에 프로그래밍 할 때
위의 PrimaryFactory를 상속한 클래스들이 Abstract Factory 들입니다.
cPrimaryFactory * Factory1st = UI.GetUserFactory1();
//첫번째 유닛 생산 명령이 떨어졌을 경우
이렇게 사용하면 되는 것입니다. 인터페이스가 통일 되었기 때문에 공장이 어느 종족의 것인지, 유닛이 구체적으로 어떤 종류의 것인지 신경을 쓸 필요가 없게 되었습니다. 만약 이 패턴을 쓰지 않고 만든다면 if문을 이용해서 세 종족의 경우를 모두 코딩 해주어야 할 것입니다. 코딩의 비용이 3배나 늘어나게 되는 셈이지요. 게다가 구조가 바뀌었을 때 3배나 되는 코드를 수정해야 하므로 유지보수 비용도 3배가 되는 셈입니다. 그러나 실제로는 3배가 훨씬 넘는다고 할 수 있습니다. 이 패턴을 쓰면 구현과 인터페이스를 분리시키기 때문에 함수 하나의 구현이 바뀐다고해서 인터페이스를 바꿀 필요도 없을 뿐더러 대부분의 경우 바뀌는 부분이 그 함수 하나 뿐일 경우가 많습니다. 그러나 if를 이용한 구조적 프로그래밍의 경우에는 함수 구현이 조금만 바뀌어도 연쇄적으로 수정해야 할 부분이 생기는 경우가 흔합니다.
이 패턴에서 객체의 생성과 사용을 분리시키는 이유는 인터페이스와 구현의 분리를 위해서라고 말씀 드렸습니다. 객체의 생성은 구현에 종속적일 수밖에 없는데 그 이유는 생성자에서 객체의 값을 할당하는 등의 설정을 하기 때문입니다. 객체의 생성이 단순히 new Object를 하는 것만을 의미하지는 않고, Object에 각종 설정을 하는 것을 모두 포함합니다. Bitmap클래스라면 new Bitmap을 해서 클래스를 만들고 비트맵을 로딩하고, 필요한 만큼 잘라내는 등등 사용하기 전에 세팅하는 것을 모두 포함하는 것입니다. 우리가 컴퓨터를 사용하듯이 말입니다. 컴퓨터 부품들을 새로 사서 조립을 하고 세팅을 하는 과정은 부품들에 따라 약간씩 다릅니다. 많이 다른 경우도 있지요. 그렇지만 일단 세팅을 다 끝내고 사용하는 것은 다 같습니다.
<적용>
- 시스템이 그것의 컴포넌트들이 어떻게 생성되고 조립되고 표현되는지(사용되는지)와 독립적이어야 할 때.
<맺음말>
p.s 원래는 factory method 패턴을 따로 하려고 했는데 위에 포함이 되었으므로 따로 하지 않겠습니다. 위에서 객체의 생성을 함수에 몰아넣고 하는 것이 factory method 패턴입니다. 그리고 factory method들을 묶어놓는 것이 abstract factory 입니다.