AbstractFactory에 대해 써주세요.

하이텔 강좌중에서...


< Abstract Factory >

'추상적인 공장' 이라고 해석해야 하나? 암튼 그런 뜻입니다.

객체 지향에서는 abstract(추상적인)이라는 말과 concrete(구체

적인)라는 말이 자주 쓰이는데, 클래스 상속에서 상대적인 개념

을 나타낼 때 주로 쓰입니다. 소녀라는 클래스가 사람이라는 클

래스를 상속했을 때 소녀 concrete 클래스라고 부르고 사람을 ab

stract 클래스라고 부릅니다. 이것은 우리가 실 생활에서 개념을

다루는 방식과 비슷합니다. 우리가 사람이라는 개념이 소녀라는

개념보다 추상적인 개념이고, 소녀라는 개념이 사람이라는 개념

보다 구체적이다라고 말하는 것과 같습니다. 또한 실 생활에서

처럼 추상적인 클래스와 구체적인 클래스는 상대적인 개념이지

요. 사람은 소녀에 비해 추상적인 개념이지만 동물이라는 개념

에 비해서는 구체적인 개념이지요. 이처럼 사람 클래스와 동물

클래스가 있다면 사람 클래스가 구체적인 클래스(concrete)가 되

고 동물 클래스가 추상적인 클래스(abstract)가 되는 것입니다.

<의도> 구체적인 클래스들을 지정하지 않고 연관되어 있는 객체들의 패

밀리를 생성하는 인터페이스를 제공하고 싶다.

책을 직역하니 대충 이런 내용이 되는군요. 이해가 안 가시리

라 믿습니다. -_-;;

<동기> 스타크래프트를 개발하는 상황을 상상해 봅시다. 빌 로퍼가 배

를 내밀며 지시를 내립니다. "세 종족 모두 서로 다른 독특한 건

물을 가지게 할것이고, 인터페이스는 모두 통일을 하게 만들어야

하느니라~~ 알겠느뇨~~" 당신이 묻는다. "왜 세 종족 모두 독

특한 건물을 가져야 합니까?" "재밌으니까!!!" 빌이 대답한다.

당신이 또 묻는다. "그럼 왜 인터페이스는 통일시켜야만 합니까

?" "인터페이스를 통일시키면 프로그래밍을 하기 편하니까!!"

라고 빌은 또 대답한다. 당신은 또 묻는다. "그렇지만 인터페

이스를 통일시키는 작업은 쉽지 않쟎습니까?" 그러자 빌이 인상

을 찌뿌리며 말한다. "쟤네들(인공지능 프로그래머)이 몸이 쫌

약하자나~~ 니가 쫌 고생해라~~ 잉?"

(... 이것이 게임회사의 현실입니다.. -_-;; )

당신은 이제 각기 다른 건물 구성을 가진 세 종족의 인터페이스

를 통일시키지 않으면 빌의 배치기에 목숨을 잃을 판입니다.

이것에 대한 좋은 해결책이 이 패턴입니다. 이 패턴은 객체들

을 생성하는 것과 사용하는 것을 분리시켜서 그 객체의 내용(구

현)에 신경을 쓰지 않고 객체를 사용할 수 있도록 해줍니다. 결

국에 이것은 인터페이스와 구현을 분리시키게 되는 것입니다.

이제 빌의 배치기에 위협당하는 상황으로 돌아가봅시다. 당신

이 어쩔 수 없는 상황에 부처님께 기도를 드리자 부처님이 당신

의 꿈에 나타나 계시를 내려 주십니다. "너의 정성이 갸륵하여

인터페이스 클래스라는 것을 알려 주겠노라!!" 할렐루야~~

class cUnit { public: virtual void Attack() = 0; //반드시 재정의 해야함 virtual void Move() = 0; virtual void Stop() = 0; virtual void HoldPosition() = 0; virtual void Special1() {} //재정의 안해도 됨 virtual void Special2() {} };

class cZealot : public cUnit { public: void Attack(); void Move(); void Stop(); void HoldPosition(); };

이것이 인터페이스 클래스입니다.. 먼저 순수 가상함수를 눈여

겨 보세요

virtual cUnit * Create1stUnit() = 0;

이것이 순수가상함수입니다.. 뒤에 = 0; 이 붙이있는 것을 보고

의아하게 생각 하시겠지만 이것은 0을 대입한다는 뜻이 아니라

구현이 없다는 뜻입니다. 이 순수 가상함수가 하나라도 있는 클

래스는 인스턴스를 생성할 수 없고 순수 가상 함수를 포함한 클

래스를 상속한 클래스는 순수 가상 함수들을 모두 재정의 해야만

인스턴스를 생성할 수 있습니다. 저 늠름한 질럿 클래스를 보시

면 Attack함수부터 HoldPosition까지 4개 모두를 재정의한 것을

알 수 있습니다. 때문에 Zealot 클래스는 인스턴스를 생성할 수

있지만 cUnit은 인스턴스를 생성할 수 없습니다. 만약 cZealot

에서 4개의 순수 가상 함수 중 하나라도 빼먹고 재정의 하지 않

았다면 컴파일러가 에러를 일으키기 때문에 cZealot의 인스턴스

도 생성할 수 없게 됩니다.. 순수 가상 함수는 실제 함수가 아

닌 인터페이스에 대한 약속이라고 보면 됩니다. 순수 가상 함수

에 대해 설명을 하다 보면 끝이 없으니 C++ 비급을 보고 열심히

연마하시길 바랍니다.

또 알아햐 할 것은 다형성입니다. 설명을 위해 하이템플러를

추가하겠습니다.

class cHiTemplar : public cUnit { public: void Attack(); void Move(); void Stop(); void HoldPosition(); void Special1(); void Special2(); //하이템플러는 마법이 세개였나? //귀찮아서 두개만... };

cZealot zealot; cHiTemplar templar;

cUnit * Unit1 = &zealot; Unit1->Attack(); //zealot.Attack()와 같은 결과

Unit1 = &templar; Unit1->Attack(); //templar.Attack()와 같은 결과

이것이 다형성입니다. Unit1에 들어간 클래스형에 따라 그 클

래스형에 맞는 함수가 호출되는 것을 알 수 있을 것입니다. 이

것 역시 자세한 것은 C++비급을 참고하세요.

이 인터페이스 클래스는 인터페이스와 구현을 확실히 분리시켜

주기 때문에 유닛을 사용에 대한 프로그래밍 할 때 유닛이 무

엇인지 신경 쓸 필요도 없게 해줍니다. 또한 이것으로 인해 모

든 유닛들을 큐나 리스트, 트리 등의 자료구조로 다룰 수 있게

되는 이점도 있습니다.

또 다른 이점은 사용할 때에는 인터페이스 클래스만 include 해

주면 되기 때문에 컴파일 연관성을 줄여주고 컴파일을 빠르게 해

준다는 것입니다.

이제 Abstract Factory의 핵심을 설명하는 것이 남았습니다.

Abstract 클래스의 핵심은 생성하는 것과 사용하는 것의 분리와

객체 패밀리의 지정입니다.

class cPrimaryFactory //1차 생산건물에 대한 { //인터페이스 클래스 public: virtual cUnit * CreateUnit1() = 0; virtual cUnit * CreateUnit2() = 0; virtual cUnit * CreateUnit3() = 0; virtual cUnit * CreateUnit4() = 0;

//베이스 클래스의 소멸자는 virtual이어야 합니다. //Effective C++참조. virtual ~cPrimaryFactory(); };

class cBarrack : public cPrimaryFactory { public: cUnit * CreateUnit1() { return new cMarine; } cUnit * CreateUnit2(); { return new cFirebat; } cUnit * CreateUnit3(); { return new cGhost; } cUnit * CreateUnit3(); { return new cMedic; } };

여기서 CreateUnit함수들의 구현을 단순히 [return new 유닛]

이렇게 해놨는데, 실제로는 이렇게 단순하지 않을 것입니다. 메

모리 유출을 피하기 위해 먼저 Object Manager같은 클래스에 포

인터를 저장하고 난 다음에 리턴하던지 하겠죠.

class cGateway : public cPrimaryFactory { public: cUnit * CreateUnit1(); //질럿 생산 cUnit * CreateUnit2(); //드래군 cUnit * CreateUnit3(); //하이템플러 cUnit * CreateUnit4(); //다크템플러 };

class cHatchery : public cPrimaryFactory { public: cUnit * CreateUnit1(); //저글링 cUnit * CreateUnit2(); //히드라 cUnit * CreateUnit3(); //??? cUnit * CreateUnit4(); //??? };

저그의 해처리는 특이한 구조이기 때문에 다른 패턴을 동원해야

합니다만 설명을 위해 단순화하기로 하겠습니다.

UML도표를 그려서 설명하면 쉬운데.. 힘들군요. 뭐~ 이 강좌는

어디까지나 소개 차원에서 하는 것이므로 실제로 이 패턴들을 써

서 프로그래밍을 하고 싶으신 분은 디자인 패턴 책을 반드시 사

시길 바랍니다. 이미 패턴을 모두 알고 있다 해도 디자인 패턴

책은 패턴 사전으로서의 가치가 크기 때문에 프로그래밍 할 때

반드시 참고하시길..

위의 PrimaryFactory를 상속한 클래스들이 Abstract Factory 들 입니다.

cPrimaryFactory * Factory1st = UI.GetUserFactory1(); //유저가 현재 선택한 종족의 //첫번째 생산 공장에 대한 포 //인터를 얻어옵니다.

//첫번째 유닛 생산 명령이 떨어졌을 경우 Factory1st->CreateUnit1(); //세번째 유닛 생산 명령이 떨어졌을 경우 Factory1st->CreateUnit3();

이렇게 사용하면 되는 것입니다. 인터페이스가 통일 되었기 때

문에 공장이 어느 종족의 것인지, 유닛이 구체적으로 어떤 종류

의 것인지 신경을 쓸 필요가 없게 되었습니다. 만약 이 패턴을

쓰지 않고 만든다면 if문을 이용해서 세 종족의 경우를 모두 코

딩 해주어야 할 것입니다. 코딩의 비용이 3배나 늘어나게 되는

셈이지요. 게다가 구조가 바뀌었을 때 3배나 되는 코드를 수정

해야 하므로 유지보수 비용도 3배가 되는 셈입니다. 그러나 실

제로는 3배가 훨씬 넘는다고 할 수 있습니다. 이 패턴을 쓰면

구현과 인터페이스를 분리시키기 때문에 함수 하나의 구현이 바

뀐다고해서 인터페이스를 바꿀 필요도 없을 뿐더러 대부분의 경

우 바뀌는 부분이 그 함수 하나 뿐일 경우가 많습니다. 그러나

if를 이용한 구조적 프로그래밍의 경우에는 함수 구현이 조금만

바뀌어도 연쇄적으로 수정해야 할 부분이 생기는 경우가 흔합니

다.

이 패턴에서 객체의 생성과 사용을 분리시키는 이유는 인터페이

스와 구현의 분리를 위해서라고 말씀 드렸습니다. 객체의 생성은

구현에 종속적일 수밖에 없는데 그 이유는 생성자에서 객체의 값

을 할당하는 등의 설정을 하기 때문입니다. 객체의 생성이 단순

히 new Object를 하는 것만을 의미하지는 않고, Object에 각종

설정을 하는 것을 모두 포함합니다. Bitmap클래스라면 new Bitm

ap을 해서 클래스를 만들고 비트맵을 로딩하고, 필요한 만큼 잘

라내는 등등 사용하기 전에 세팅하는 것을 모두 포함하는 것입니

다. 우리가 컴퓨터를 사용하듯이 말입니다. 컴퓨터 부품들을

새로 사서 조립을 하고 세팅을 하는 과정은 부품들에 따라 약간

씩 다릅니다. 많이 다른 경우도 있지요. 그렇지만 일단 세팅을

다 끝내고 사용하는 것은 다 같습니다.

<적용> 이럴 때 Abstract Factory 패턴을 사용하라는군요

- 시스템이 그것의 컴포넌트들이 어떻게 생성되고 조립되고 표현

되는지(사용되는지)와 독립적이어야 할 때.

- 시스템이 다양한 컴포넌트의 패밀리 중 하나로 설정되어야 할

- 관련된 컴포넌트들의 패밀리가 함께 쓰여야 하도록 디자인 되

었고, 이런 구속을 강제해야만 할 때

- 클래스 라이브러리를 제공 하려는데 그것의 구현을 드러내지

않고 인터페이스만 드러내길 원할 때

AbstractFactory (last edited 2005-02-11 08:34:22 by )