Command 패턴

[[ Command 패턴 ]]

< 의도 >
객체의 request를 캡슐화해서 각각 다른 request를 매개변수화(parameterize)할 수 있도록 한다. 그래서 그것들을 queue에 집어넣거나 기록(log)하거나,undo등을 지원하도록 한다.

< 동기 >
또다시 스타크래프트의 유닛 클래스를 만드는 상황을 생각해 봅시다. (맨날 스타크래프트의 예만 드는군요. 저는 스타크를 별로 좋아하지 않지만 대중성있고 잘 만들어진 게임이기 때문에 스타크의 예를 드는 것이예요~ ) 

지난번의 상태 패턴에 이어서 유닛 클래스를 만들 경우를 생각해 봅시다. 스타에서 지원하는 웨이포인트를 모르시는 분은 없을 것입니다. 큐에다 이동 명령들을 쌓아놓고 실행하도록 하는 것인데 오직 이동 명령만 넣을 수가 있습니다. 그러나 Earth2140이던가? 그 게임에서는 모든 명령을 큐에다 넣고 실행 할 수 있었던 걸로 기억 되는군요. 작전 명령을 내려놓고, "준비 땅!" 하면 작전 명령에 따라 한 단계씩 수행하는 것입니다. 정말 편리한 기능이죠. 그것을 보고 뚱띵이 빌로퍼가 말했다고 칩시다. "우리도 저 기능을 넣자!! 이게 니 몫이라는건 알고 있겠지?" 
당신은 찍소리도 못하고 만들어야만 할 것입니다. 이것을 구현하려면 Queue가 필요할 것입니다. 그러나 보통의 자료를 저장하는 큐가 아닌 기능을 저장하는 큐가 필요할 것입니다. 그럼 어떻게 기능을 자료처럼 큐에 저장할 것이냐? 아마도 그 방법 중 하나로서 함수포인터가 있을 것입니다. 그러나 지난 강좌에서 말씀 드렸다시피 함수포인터보다 더 많은 장점을 가진 것이 있습니다. 바로 가상함수와 인터페이스 클래스를 이용한 함수포인터의 구현!! 이것을 이용해 Queue, log Undo, list, Tree 등등을 구현하는 것이 바로 이 Command 패턴인 것입니다. 사실 이 가상함수와 인터페이스 클래스를 이용한 함수포인터는 대부분의 패턴의 기본이 됩니다. 이것을 응용해서 만들어진 것들이 이 패턴이라고도 할 수 있고, 이 패턴들이 모두 이것으로 통한다고도 할 수 있습니다. 

< 소스 >
그렇다면!! 이제 Command패턴의 구현에 대해서 알아보도록 합시다요~.

class ICommand //각 명령들의 인터페이스 클래스
{
public:
virtual void Execute() = 0;
};
//전방 참조
class cCommand_Attack;
class cCommand_Move;
class cCommand_Stop;
class cCommand_HoldPosition;
class cCommand_Patrol;

class IUnit
{
IState * _pState; //state패턴 강좌에서 cZealot클래스에 있었던
//변수를 여기로 옮겨옴
public:
//Shift가 눌렸을 때 이 함수가 호출되겠죠.
virtual void ChangeToPlanMode( IUnit *& pPtrUnit )
{
//cPlanDecorator를 붙인다.
new cPlanDecorator( pPtrUnit );
}
//Shift가 떼어졌을 때 이 함수가 호출
virtual void ChangeToNormalMode()
{}

//상태에 대한 ID를 리턴하는 함수( state패턴 강좌에서 IState클래스가
// 수정 되었습니다)
ID_STATE GetStateID()
{
return _pState->GetID();
}
//나머지는 지난번 강좌의 IUnit클래스와 같음
....
....
};

//cUnitDecorator는 지난번 강좌(Decorator)에서 나왔던 그 놈임.
class cPlanDecorator : public cUnitDecorator
{
IUnit * _pActor;
queue< ICommand * > * _pCommandQ; //STL의 queue를 사용
bool _IsInputMode; //명령의 입력 모드인가?(true)
//아니면 실행 모드인가?(false)
public:
cPlanDecorator( IUnit *& pActor )
: cUnitDecorator( pActor ), _pActor( pActor ), _IsInputMode(true)
{}

void ChangeToPlanMode( IUnit *& pPtrUnit )
{
_IsInputMode = true; 
}
void ChangeToNormalMode()
{
_IsInputMode = false;

void ClearPlan()
{
_pCommandQ->clear();
}
void Attack( IUnit * Target ) 

//Shift를 누른 상태로 공격 명령을 내리면 명령 추가
if( _IsInputMode )
_pCommandQ->push( new cCommand_Attack( Target ) );
//Shift를 뗀 상태로 공격 명령을 내리면 큐의 모든 명령이 취소되
//고, 공격 명령을 바로 실행.
else 
{
_pActor->Attack( Target );
SelfDestroy(); 

}
void Move( int x, int y ) 
{
//Attack과 마찬가지..
if( _IsInputMode )
_pCommandQ->push( new cCommand_Move( x, y ) );
else
{
_pActor->Move( x, y );
SelfDestroy();
}
}
void Stop()
{
//Stop명령은 큐에 집어넣을 수 없음.
if( !_IsInputMode )
{
_pActor->Stop();
SelfDestory();
}
}
//HoldPosition, Patrol등은 귀찮으니 생략함..
//일꾼 같으면 Build명령이 있을테고, 마법유닛은 Special명령이 있겠지만
//이것도 모두 위의 Attack이나 Move처럼 처리하면 됩니다.



void Update()
{
//Stop, HoldPosition, Patrol등 무한히 지속되는 명령들이 아닌
//나머지 명령들은 명령수행을 마쳤을 때 Stop상태로 바뀌므로
//Stop상태가 되었을 때 다음 명령을 실행.
if( _pActor->GetStateID() == IState::STATE_STOP )
{
if( !_pCommandQ->empty() )
{
ICommand * pCommand = _pCommandQ->front();
pCommand->Execute();
delete pCommand;
}
else
{
SelfDestroy();
}
}

_pActor->Update();
}
};

class cCommand_Attack : public ICommand
{
IUnit * _pActor;
IUnit * _pTarget;
public:
//주의!! 여기서는 편의상 생성자의 선언과 정의를 한꺼번 
//에 해서 인라인이 되어 버렸지만 생성자를 인라인으로 만 
//드는 것은 좋지 않습니다.자세한건 Effective C++참조
cCommand_Attack( IUnit * pActor, IUnit * pTarget )
: _pActor( pActor ), _pTarget( pTarget )
{
}
void Execute()
{
_pActor->Attack( _pTarget );
}
};

class cCommand_Move : public ICommand
{
IUnit * _pActor;
int _DestX, _DestY;
public:
cCommand_Move( IUnit * pActor, int x, int y )
: _pActor( pActor ), _DestX( x ), _DestY( y )
{ }
void Execute()
{
_pActor->Move( _DestX, _DestY );
}
};

//이런 식으로 사용하게 되겠죠.
void OnKeyDown( int KeyCode )
{
if( KeyCode == SHIFT )
{
IUnit &* pSelectedUnit = GetSelectedUnit();
pSelectedUnit->ChangeToPlanMode( pSeletecdUnit );
}
}

void OnKeyUp( int KeyCode )
{
if( KeyCode == SHIFT )
{
IUnit &* pSelectedUnit = GetSelectedUnit();
pSelectedUnit->ChangeToNormalMode();
}
}

이 외에도 Command 패턴은 많은 곳에서 쓰입니다. 비동기적인 처리를 위해 어떤 메시지를 받고 나서 수행은 나중에 할 때라던가, 우선순위 큐에 넣어서 이벤트 시스템에 쓰이기도 합니다. undo, redo에서도 사용되구요.
Attack과 Move 명령만 구현했는데, 나머지 명령들도 비슷한 방법으로 구현하시면 됩니다.

위에서 만든 명령 예약 기능은 지난 강좌에서 했던 State패턴과 잘 연동되어 돌아갈 것입니다. 유닛이 구체적으로 무엇인지와 관계 없이 그의 인터페이스를 그대로 호출하기 때문이지요. 각 유닛은 Attack, Move 등등의 명령 함수들만 재정의 해주면 되는 것입니다.

위에서 구현한 것과 다른 방식이 있는데, 각각의 Command클래스에 구현 자체를 전부 넣어버리는 방식입니다. 위에서는 Command클래스의 Execute가 호출되면 유닛의 함수를 호출하기만 하는데, (이걸 '대리'한다고 합니다.) 이 '대리'를 하지 않고 Execute함수에다 구현을 해서 필요한 일을 모두 Execute함수 내에서 수행하는 방식입니다.

위의 것을 이해하셨다면 undo나 redo, log등도 만드실 수 있을 것입니다. undo, redo의 예제는 디자인 패턴 책에 나와있으니 필요하신 분은 책을 참고하세요.

이 패턴을 응용하면 다양한 자료구조에 모두 적용할 수 있습니다. tree, list, 우선순위 큐, 해쉬 등등... 


p.s 주의사항!! 제 패턴 강좌의 소스들은 컴파일을 해보지 않은 코드들이기 때문에 실제 사용시에는 문제를 일으킬 수도 있습니다. 때문에 위의 소스 코드는 이해의 목적으로만 사용하시고 복사, 붙여넣기를 해서 사용하진 마세요.

Posted by Junios

2009/02/26 18:03 2009/02/26 18:03
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/238

Decorator 패턴

[[ Decorator 패턴 ]]

< 의도 >
동적으로 (실행시간중에) 객체에 부가적인 기능을 붙여주고 싶다. 이 패턴은 기능을 확장하기 위해 서브클래싱(상속)을 하는 것에 대한 유연한 대안을 제시해준다.

< 동기 >
..이번에도 스타크래프트 개발 현장..

유닛의 렌더링을 만들어야 하는 상황이라고 생각 해봅시다. 저의강좌에서는 상당히 늦게 언급하게 됐는데, 유닛의 렌더링같은 경우에는 다른 어떤 부분보다도 먼저 만들게 되겠죠.

빌로퍼가 말합니다. "이제는 유닛의 렌더링을 만들어야 한단다. 근데 문제가 그렇게 간단한건 아니고 여러 가지 마법 효과와 상태 효과를 잘 정리정돈해서 출력해야 하기 때문에 문제란다~. 이걸 잘 정리정돈하지 않으면 계속 개발을 하기가 힘들어지자너. 기능도 계속 추가해야되고, 기획때문에 무쟈게 뜯어고치기도 해야자나 그래서 좋은 구조를 만들어야되는데, 너밖에 기대할 사람이 없거든~.. 니가 수고좀 해라잉~"

드뎌 짐을 떠맡았습니다. 이젠 대우가 쫌 좋아진것 같군요..당신은 고민을 합니다.

"이것을 어떻게 해결해야 할까.. DirectDraw를 쓴다면 각각의 유닛들이 그 유닛의 그림을 나타내는 surface들을 가지고 있을테고, 그 surface를 백서피스에 그려주는 Draw 함수를 가지고 있을 것이다. 

흠.. 여기까진 문제가 없는데.. 마법이랑, 하단의 상태창등이 문제란 말이야.. 먼저 마법을 생각해보자. 마법을 어떻게 렌더링 하도록 해야 하나? 마법을 객체로 만들어서 일괄적으로 렌더링 하도록 하면 될것 같은데.. Defensive Matrix처럼 유닛을 따라다녀야 하는 마법이 문제로군.. 흠.. 게다가 하단의 상태표시창에 특별한 표시를 해야 하는 경우도 있으니.. 이런 마법이나 상태창을 표시하는 기능을 특정한 유닛들에만 붙일 수 있어야 하는데.. Unit클래스 내에 변수를 만들고, Draw에서 변수값에 따라 if로 처리하도록 할까? 그렇게 하면 if에 의한 하드코딩이 많아져서 유연하지도않고 복잡하게 얽히게 될텐데.. 상속을 이용할까? cTank가 락다운이 걸린다면 cLockDownedTank로 바뀌도록 한다면? 여기다 디펜시브 매트릭스까지 걸린다면? cLockDowned_DMatrixed_Tank로 바뀌도록 해야 하나? 으악~!! 이렇게 하면 모든 마법-유닛 조합마다 클래스를 만들어줘야 하는데... 게다가 마법-유닛 조합이 정해져 있어서 전혀 유연하지가 않단 말이야. 흐~ 어떻게 해야되낭... 어떻게.. 어떻게..헉헉.. 어떻게..헉.. 어떻..헥.. 어떻.. 어.. 어.. 어.. ....."

이런~ 쓰러졌군요. 프로그래밍이란게 상당히 힘든거 같습니다. 

이러한 상황을 잘 해결해 줄 수 있는 것이 데코레이터 패턴입니다. 데코레이터 패턴은 링크드 리스트에서 삽입을 하는 것처럼 추가할 기능을 원래의 객체 앞에다 붙이는 것입니다.(포인터를 이용해서) 

클라이언트 코드가 탱크 클래스 인스턴스를 참조하고 있다면(포인터로 가리키고 있다면)



(클래스에 있어서 클라이언트는 그 클래스를 사용하는 부분의 코드를 말합니다. 다른 클래스이거나 main함수등이겠죠. 네트웍에서의 클라이언트가 아님)

여기에 탱크가 락다운에 걸렸다면 aTank 앞에다 aLockDown을 붙이는 것입니다. 



이 붙여진 객체(락다운)을 decorator라고 부르고, 이렇게 붙이는 것을 decorate한다고 합니다. decorator는 그것이 decorate하는 객체의 인터페이스와 통일시키기 때문에 그것의 존재는 투명하게 됩니다.(클라이언트에게는 보이지 않는다는 말임) 인터페이스를 통일시킨다는 것은 같은 인터페이스 클래스로부터 상속받는다는 것입니다. 그리고 클라이언트에서는 탱크나 락다운의 포인터가 아닌 인터페이스 클래스의 포인터를 저장하게 되기 때문에 클라이언트로서는 락다운과 탱크의 구분이 필요 없게 되는 것이지요.
인터페이스 클래스가 IUnit이라고 하면 클라이언트의 관점에서는 이렇게 보이겠죠.


클라이언트는 인터페이스 클래스인 IUnit의 포인터로 가지기 때문에 그 유닛이 무엇인지, 기능이 몇개가 덧붙여졌는지 상관할 필요가 없이 원래의 객체를 사용하듯이 사용하면 되는 것입니다. 
위의 락다운 걸린 탱크에 디펜시브 매트릭스를 걸고, 그 탱크를 플레이어가 선택했다고 하면..

+--

이렇게 되겠죠. aRenderState는 유닛의 상태를 하단의 상태바에 출력하는 기능의 클래스입니다. 
< 소스 >

class IUnit
{
public:
//데코레이터에서 사용하는 포인터
IUnit * _pNext;
IUnit() : _pNext( NULL ) {}
virtual ~IUnit() {}

virtual void Attack() = 0;
virtual void Move() = 0;
virtual void Damage(int) = 0; //공격 당했을 때 에너지 
//를 닳게함
virtual void Update() = 0;
virtual void Draw() = 0;
virtual void GetState() = 0;
};

class cTank : public IUnit
{
public:
void Attack();
void Move();
void Damage();

void Update();
void Draw();

void GetState();
};

//클래스가 스스로를 소멸시키지 못하므로 소멸을 대행해서 일괄적으로 처 
//분 하는 클래스
class cGarbages
{
//쓰레기들의 저장공간
vector< void * > _pContainer; //STL의 vector클래스
public:
cGarbages & GetInstance(); //싱글턴

void Add( void * ); //쓰레기 추가
void Disposal(); //쓰레기 모두 처분(전부 delete)
};

class cUnitDecorator : public IUnit
{
//(클라이언트 코드에서의 유닛에 대한 포인터)의 포인터
//데코레이터 리스트에서의 Head라고 할 수 있다.
IUnit ** _pPtr;
public:
cUnitDecorator( IUnit *& pNext )
: _pPtr( &pNext )
{
_pNext = pNext;
*_pPtr = this;
}

//디폴트 구현을 제공함.
virtual void Attack() { _pNext->Attack(); }
virtual void Move() { _pNext->Move(); }
virtual void Damage(int dmg) { _pNext->Damage(dmg); }
virtual void Update() { _pNext->Update(); }
virtual void Draw() { _pNext->Draw(); }

//상태 정보 리턴. sState는 유닛의 상태정보를 담는 구조체
virtual sState GetState() { return _pNext->GetState(); }

//스스로 데코레이터들의 리스트에서 삭제한다.
void SelfDestroy()
{
if( *_pPtr == this )
*_pPtr = _pNext; 
else
{
//리스트에서 이것의 앞에 있는 데코레이터를 찾는다.
IUnit * pUnit = *_pPtr; 
while( pUnit->_pNext != this )
{
pUnit = pUnit->_pNext;
}
pUnit->_pNext = _pNext;


cGarbages::GetInstance().Add( this ); //소멸
}
};

class cLockDown : public cUnitDecorator
{
int _Duration; //지속시간
LPDIRECTDRAWSURFACE _pSurface; //락다운의 그림을 저장하는 서피스
public:
cLockDown( cUnit * pNext )
: cUnitDecorator( pNext )
{}
void Attack() {} //락다운에 당하면 아무 일도 할 수 없죠. 
void Move() {} //그래서 명령을 내려도 아무일도 하지 않음 
void Draw()
{
_pNext->Draw(); //원래의 탱크를 화면에 출력한 다음
_pSurface->BltFast(); //그 위에 락다운의 그림을 출력한다.
}
void Update() 

_Duration--;
//지속시간이 지나면 자동 소멸
if( _Duration <= 0 )
SelfDestroy();

_pNext->Update();

};

//Defensive Matrix
class cDMatrix : public cUnitDecorator
{
int _Energy; //디펜시브 매트릭스의 에너지
int _Duration; //지속시간
public:
void Damage(int dmg)
{
//데미지가 디펜시브 매트릭스에 가해진다.
_Energy -= dmg;
//에너지가 0이 되면 파괴
if( _Energy <= 0 )
{
SelfDestroy(); //소멸
}
//디펜시브 매트릭스가 있는 동안은 유닛에가 데미지가 
//가지 않기 때문에 유닛의 Damage함수는 호출하지 않는
//다.
}
void Draw()
{
_pNext->Draw(); //원래의 탱크를 화면에 출력한 다음
_pSurface->BltFast(); //그 위에 디펜시브매트릭스 그림을 출력
}
void Update()
{
_Duration--;
//지속시간이 지나면 자동 소멸
if( _Duration <= 0 )
SelfDestroy();

_pNext->Update();
}
};

class cRenderState : public cUnitDecorator //상태창에 유닛 상태 출력
{
public:
cRenderState( IUnit * pUnit );
void Draw()
{
//상태를 얻어온 다음에..
sState TankState = _pNext->GetState();
//...탱크의 상태를 하단의 상태창에 출력...
}
};

//클라이언트 코드( 대충 이런 식으로 사용하게 되겠죠 )
class cGameManager 
{
IUnit * pTank;
public:
void OnClickLockDown() 
{
....
//이제 탱크가 락다운에 걸렸습니다. 
_pTank = new cLockDown( _pTank );
}
void OnClickDMatrix()
{
....
//디펜시브 매트릭스 명령이 내려지자 탱크가 마법에 걸립니다.
_pTank = new cDMatrix( _pTank );
}
void OnClickUnit( IUnit * pUnit )
{
//이제 이 탱크를 플레이어가 선택 했습니다.
pTank = new cRenderState( pTank );
}
void Update()
{
...
//이렇게 해서 탱크를 화면에 출력할 것입니다.
_pTank->Draw();
_pTank->Update();
//매 틱마다 쓰레기들을 처분합니다.
//이것땜에 속도가 저하되는게 싫으시다면 뭐~ 10틱이나 20틱, 또는
1초에
//한번씩 처분하도록 해도 되겠죠~
cGarbages::GetInstance().Disposal();
}
};

//Psynoic Storm같은 경우는 특정 유닛에게 거는 마법이 아니기 때문에 데
//코레이터를 쓰지 말고 독립된 객체로 처리해야 하겠군요.

위에서 *&(포인터 레퍼런스)를 써서 클래스를 가리키는 포인터를 조작하도록 했는데, 이렇게 쓰려면 주의해야 할 것이 있습니다. 클래스를 가리키는 포인터가 단 하나!!이어야 한다는 것.(Composite패턴을 쓰면 저절로 그렇게 되죠)

커헉! 힘들다. 위의 예제는 디자인 패턴 책하고 쪼금 다릅니다. 실시간중에 데코레이터들이 붙여졌다 떼어졌다 하기 때문에...

< 응용(적용?) >
이럴때 데코레이터를 사용합니다.

-개개의 객체에 동적이고 투명하게(다른 객체에 영향을 미치지 않고) 기능을 추가하기 위해서

-특정 기능을 독립적인 클래스로 만들기 위해서

-서브클래싱을 통해 확장을 하는 것이 좋지 않을 때, 서브클래싱을 통한 확장이 가능하지만 이럴 경우 가능한 모든 기능들의 조합을 모두 클래스로 만들어야 하기 때문에 효율적이지 못할 때

-클래스 정의가 감추어져 있거나 서브클래싱이 불가능할때(소스없이 헤더랑 라이브러리 파일들만 공개하는 경우 상속하기가 곤란하죠. 이럴때 ..)

< 결과 >
-정적인 상속보다 더 유연하다. 데코레이터는 실시간(run-time)중에 기능을 붙이고, 뗄 수 있다. 반면 상속은 각각의 기능이 추가된 서브클래스를 새로 만들어야 한다. (LockDowned_DMatrixed_Tank?) 이것은 클래스의 숫자를 증가시키고 시스템을 복잡하게 만든다. 게다가 데코레이터는 기능들을 조합해서 쓸 수 있게 해준다. 데코레이터는 같은 기능을 중첩(2개 이상)해서 붙일 수 있게 해준다. 락다운에 두 번 걸린 탱크가 있다면 aLockDown 데코레이터를 탱크에다 두 개 붙여주면 되는 것이다.

-쓰지 않는 기능을 위해 비용을 지불하지 않는다. 데코레이터를 붙이지 않은 유닛 클래스는 단지 그냥 유닛 클래스일 뿐이다. 새로운 기능을 위해 if검사 따위도 하지 않기 때문에 성능 저하가 없다. 

-확장이 아주 쉽다. 상속을 통한 확장은 슈퍼클래스의 클래스 정의에 대해 알고 있어야 한다. 때문에 슈퍼클래스가 복잡할 경우에는 확장을 하기도 복잡해지는 경향이 있다. 그러나 데코레이터는 원래의 클래스와 독립적으로 부가된 기능에만 신경쓰면 되기 때문에 원래의 클래스가 복잡하다 하더라도 간단하게 확장할 수 있다.

-많은 수의 작은 객체들.. 객체들을 생성할 때 new, delete를 사용한다면 성능 저하가 나타날 것이다. 이럴땐 Memory Pool을 만들어서 쓰면 성능 저하를 막을 수 있다.(Effective C++참조) 

이러한 작은 객체들은 서로간의 연결 관계에 따라 달라지지만 거의 비슷하게 보이게 될 것이다. 때문에 알아보기가 힘들고, 디버그도 힘들어지게 될 것이다.

-성능 저하.
위의 소스에서 cRenderState클래스를 보자. 이 클래스는 단지 Draw함수에 기능을 추가하기 위해 데코레이터를 썼는데, 나머지 함수 5개는 아무 일없이 함수를 한 번씩 더 거치게 된다. 때문에 약간의 성능 저하가 있게 된다. 성능 저하라고 해봤자 아주 약간일 뿐이지만, 속도가 중요한 부분에서는 좋지 않을 것이다. 이런 경우에는 Strategy 패턴을 쓰는 것이 좋다. 그 외에 컴포넌트 클래스가 무겁다면(변수나 함수가 많다면) 데코레이터에는 쓰지도 않는 변수들이 들어가게 되므로 그 또한 메모리 낭비나 성능 저하의 원인이 될 수 있다. 이럴 때에도 Strategy 패턴을 쓰는 것이 좋다. 
(Strategy패턴 강좌는 곧 하겠습니다.. )


p.s 끝까지 봐주셔서 캄사합니다. (꾸벅) 
다음은 Command패턴이 될듯 하군요.

Posted by Junios

2009/02/26 18:02 2009/02/26 18:02
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/237

State 패턴

[[ State 패턴 ]]

< 의도 >
객체의 내부 상태가 바뀌었을 때 그것의 행동까지 바뀌게 하고 싶다.

< 동기 >
...스타크래프트 개발 현장...
빌로퍼가 또다시 배를 내밀며 명령을 내립니다. "지난번의 Abstract Factory 패턴으로 만든 그것에 덧붙여 유닛 클래스를 구현해야 하느니라~. 유닛은 현재 상태에 따라 행동을 하고 어떤 자극에 대해 반응을 하느니라~. Attack 명령을 내렸다면 유닛이 Attack 상태로 바뀌고 그에 맞는 행동인 '보이는 적은 뭐든지 공격한다'를 실행하게 되느니라~. Hold Position같은 경우에는 유닛이 Hold Position상태로 바뀌게 되고 '그 자리에서 이동하지 않으면서 공격 가능한 적을 공격한다'를 실행하게 된다. 이렇게 만들어야 되는데, 문제는 같은 상태에 대해 같은 행동을 하는 유닛들이 많다는 것이니라~. 예를 들어 질럿이랑 드래군등은 Stop 상태에서 공격 받았을 때 '반격한다'라는 반응을 나타내는 것으로서 동일하지만, 하이템플러 같은 경우에는 '도망간다'라는 반응을 나타내기 때문에 같지 않은 것이다. 이처럼 질럿이랑 드래군은 상태에 대한 행동이 같은데 똑같은걸 두 번 코딩할 필요는 없잖냐...~~. 이것을 니가 해결해봐라잉~. 배치기가 무섭지 않다면 농땡이를 부려도 될 것이야~~" 이렇게 명령이 떨어졌습니다.

당신은 배치기가 무서우므로 농땡이를 피울 수도 없을 것입니다. 그러나 딱히 좋은 방법이 생각나지도 않습니다. 그러자 당신은 외칩니다. 

"도와줘~!! 졸라맨!!"

그러자 졸라맨이 막대기를 휘날리며 나타납니다. 

%_TEXT_% 짜잔~~ ( ;;-_-;;; )
|\
/ \

그러나 그의 못미더운 모습을 보고 당신은 그를 무시하고(--+) 책을 뒤져보기 시작하더니 마침내 State패턴을 발견하고는 환희의 비명을 지릅니다. 꺄아~

< 소스 >
State패턴은 각각의 상태를 클래스로 만들고 인터페이스 클래스인 IState를 상속합니다. 위에서 설명한 '가상함수를 이용한 함수포인터'와 비슷합니다. 

//---------------------------------------------------------
class IState; //클래스 전방선언

class cZealot : public cUnit //cUnit은 지난 강좌에서
{ //나왔던 그거...
IState * _pState;
void ChangeState( IState * );
public:
cZealot();
~cZealot();

//이 명령들이 전달되면 유닛은 상태를 바꾸게 됩니다.
void Attack( cUnit * Target );
void Move( int x, int y ); 
void Stop();
void HoldPosition();
void Patrol( int, int, int, int );
void Update();
};
//----------------------------------------------------------
class IState {
public:
enum ID_STATE
{
STATE_ATTACK,
STATE_MOVE,
STATE_STOP,
STATE_HOLDPOSITION,
STATE_PATROL
};
private:
const ID_STATE _ID;
public:
IState( ID_STATE state ): _ID( state )
{}

ID_STATE GetID()
{
return _ID;
}
//상태에 대한 행동하는 함수.
virtual void Operate() = 0;
};

//Attack 상태. 공격 대상(target)과 행동의 주체(pUnit)을 인
//자로 받고 멤버 변수에 저장해놓습니다. 그리고 Operate함수
//에서 멤버변수에 들어간 인자값을 참조하면서 행동을 수행합니
//다.
class cState_Attack : public IState {
cUnit * _pTarget;
cUnit * _pUnit;
public:
//Attack이 끝나면 Stop상태로 바꿔야 하기 때문에 cUnit의 
//포인터를 받음
cState_Attack( cUnit * target, cUnit * pUnit )
:IState( STATE_ATTACK ), _pTarget( target ), _pUnit( pUnit )
{}
void Operate(); //각각의 상태에 대한 행동을 
//구현함
};

class cState_Move : public IState {
int _x, _y;
cUnit * _pUnit;
public:
cState_Move( int x, int y, cUnit * pUnit );
void Operate();
};

class cState_Stop : public IState {
public:
void Operate();
};

class cState_HoldPosition : public IState {
public:
void Operate();
};

class cState_Patrol : public IState {
int _x1, _y1, _x2, _y1;
public:
cState_Patrol( int x, int y, int x2, int y2 );
void Operate();
};
//---------------------------------------------------------
cZealot::cZealot()
{
//상태 객체에 넘겨줄 인자가 모두 동일하다면 new, delete 
//보다는 싱글턴을 쓰는 것이 좋을 것입니다.
_pState = new cState_Stop;
}
cZealot::~cZealot()
{
delete _pState;
}
void cZealot::ChangeState( IState * pState )
{
//상태 전이에 대한 특별한 처리가 필요하다면 여기에 코딩 
//하면 됩니다.
delete _pState;
_pState = pState;
}
void cZealot::Attack( cUnit * Target )
{
ChangeState( new cState_Attack( Target, this ) );
}
void cZealot::Move(int x, int y )
{
ChangeState( new cState_Move( x, y, this ) );
}
void cZealot::Stop()
{
ChangeState( new cState_Stop );
}
void cZealot::HoldPosition()
{
ChangeState( new cState_HoldPosition );
}
void cZealot::Patrol( int x1, int y1, int x2, int y2 )
{
ChangeState( new cState_Patrol( x1, y1, x2, y2 ) );
}
void cZealot::Update()
{
//여기서 행동이 일어나게 됩니다.
_pState->Operate();
}
//---------------------------------------------------------

위에서 상태가 바뀔때마다 new, delete를 하게 되는데 이렇게 
하면 속도가 많이 떨어지게 됩니다. 여기에다 메모리 풀(Pool)
을 만들어서 쓰면 성능을 개선시킬 수 있을 것입니다. 실험 결과 
5배정도의 속도 향상이 있는 것 같더군요. 메모리 풀은 Effecti
ve C++에 나왔으므로 그 책을 참고하시기 바랍니다. 시간나면 
제가 강좌를 쓰게 될지도 모르겠군요( 기대하진 마세요 )
//---------------------------------------------------------
< 결과 >
State패턴은 이런 장점들을 가지고 있습니다.
1. 특정 상태에 종속적인 행위를 다른 상태와 분리시킨다.
분류를 명확히 해놓는 것은 유지 보수를 위해 아주 중요한 것이죠. switch문을 통한 코딩과 State패턴을 이용한 것의 차이는 정리 안된 문서와 잘 분류되어 정리된 문서의 차이와 할 수 있겠습니다.
2. 상태의 전이를 명시적으로 만든다.
객체가 그것의 현재 상태를 단순히 내부 데이터 값으로 나타 상태의 전이가 단순히 값의 대입이기 때문에 명시적이지 못할 것입니다. 때문에 상태 전이에 대한 처리를 하기가 애매할 것입니다. if문을 남발하는 지저분한 구조가 되겠 지요. 반면에, 상태들에 대한 각각의 객체를 정의하는 것은 상태의 전이를 보다 명시적으로 만들게 됩니다. 또한 상태 객체를 이용하면 특정 상태로 전이되는 것에 대해 각 상태 클래스마다 함수를 만들어주면 되기 때문에 훨씬 유연하고 유지보수가 용이한 구조를 만들 수 있을 것입니다. 위의 소스에서는 상태의 전이에 대한 특별한 처리는 하지 않았기 때문에 필요한 분은 스스로 고안해서 쓰세요. 별로 어렵지 않을 껍니다.
3. State 객체들을 여러 클래스에서 공유할 수 있다. Attack에 대한 상태를 생각해본다면, 질럿은 공격 당했을 때 반격을 할 것이지만 하이템플러는 도망을 갈 것입니다. 때문에 서로 다른 상태 클래스를 사용하겠지만 질럿이랑 드래군은 공격을 받았을 때 똑같이 반격을 할 것이므로 Attack에 대한 상태 클래스를 공유할 수 있습니다. 

4. 더 유연하다.
switch문과 같은 방법은 정적이기 때문에 유닛의 상태에 대 한 행위를 바꿀려면 하드코딩을 해야 합니다. 그러나 State 패턴을 이용해서 다양한 상태-행위 클래스를 만들어놓고 스크립트에서 그것들을 조합해서 조립하면 유지보수, 확장등의 비용을 훨씬 줄일 수도 있고 스크립트만으로도 별 희한한 게임을 다 만들 수 있게 될 것입니다.

//---------------------------------------------------------
http://www.uml.co.kr/ 
디자인 패턴과 UML을 위한 좋은 홈페이지입니다. 한 번 들려보세요
한글로 된 강좌도 읽을 수 있고, 객체 지향에 대한 생소한 분야들을 알게 될 것입니다.


으흑~ 이번엔 데코레이터 패턴입니다. 이 패턴에 대한 예를 찾기가 힘들
더군요. 스타를 하다가 우연히 예를 발견해서 강좌를 씁니다. 제가 첨에 
패턴 배울 때 이 데코레이터 패턴이 이해하기가 가장 힘들었었는데 그 때문
인지 강좌 쓰기도 힘드는군요 -_-;;;;

출처 : 불명.

Posted by Junios

2009/02/26 18:01 2009/02/26 18:01
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/236

Abstract Factory 패턴

[[ Abstract Factory 패턴]]

'추상적인 공장' 이라고 해석해야 하나? 암튼 그런 뜻입니다. 객체 지향에서는 abstract(추상적인)이라는 말과 concrete(구체적인)라는 말이 자주 쓰이는데, 클래스 상속에서 상대적인 개념을 나타낼 때 주로 쓰입니다. 소녀라는 클래스가 사람이라는 클래스를 상속했을 때 소녀를 concrete 클래스라고 부르고 사람을 abstract 클래스라고 부릅니다. 이것은 우리가 실 생활에서 개념을 다루는 방식과 비슷합니다. 우리가 사람이라는 개념이 소녀라는 개념보다 추상적인 개념이고, 소녀라는 개념이 사람이라는 개념보다 구체적이다라고 말하는 것과 같습니다. 또한 실 생활에서처럼 추상적인 클래스와 구체적인 클래스는 상대적인 개념이지요. 사람은 소녀에 비해 추상적인 개념이지만 동물이라는 개념에 비해서는 구체적인 개념이지요. 이처럼 사람 클래스와 동물 클래스가 있다면 사람 클래스가 구체적인 클래스(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 Bitmap을 해서 클래스를 만들고 비트맵을 로딩하고, 필요한 만큼 잘라내는 등등 사용하기 전에 세팅하는 것을 모두 포함하는 것입니다. 우리가 컴퓨터를 사용하듯이 말입니다. 컴퓨터 부품들을 새로 사서 조립을 하고 세팅을 하는 과정은 부품들에 따라 약간씩 다릅니다. 많이 다른 경우도 있지요. 그렇지만 일단 세팅을 다 끝내고 사용하는 것은 다 같습니다. 

<적용>
이럴 때 Abstract Factory 패턴을 사용하라는군요
- 시스템이 그것의 컴포넌트들이 어떻게 생성되고 조립되고 표현되는지(사용되는지)와 독립적이어야 할 때.
- 시스템이 다양한 컴포넌트의 패밀리 중 하나로 설정되어야 할 때
- 관련된 컴포넌트들의 패밀리가 함께 쓰여야 하도록 디자인 되 었고, 이런 구속을 강제해야만 할 때
- 클래스 라이브러리를 제공 하려는데 그것의 구현을 드러내지 않고 인터페이스만 드러내길 원할 때

<맺음말>
어쩌다 보니 인터페이스 클래스와 abstract Factory패턴, Factory Method패턴까지 섞여버렸습니다. 원래 디자인 패턴에서는 디자인 시의 의사소통을 위해 패턴들의 개념을 명확히 구분하는데, 이 강좌의 목적은 어디까지나 소개이므로 '디자인 패턴은 좋은 것이다!'라는 것에 초점을 맞추어 설명을 했습니다. 계속 강조하지만 디자인 패턴을 실제로 써먹으실 분들은 책을 사서 제대로 공부하시길 바랍니다. 

p.s 원래는 factory method 패턴을 따로 하려고 했는데 위에 포함이 되었으므로 따로 하지 않겠습니다. 위에서 객체의 생성을 함수에 몰아넣고 하는 것이 factory method 패턴입니다. 그리고 factory method들을 묶어놓는 것이 abstract factory 입니다.

p.s 예전에 C&C를 좋아하며 Rule을 에디트하는 에디터를 받아서 열심히 했던 적이 있었습니다. 저 에디터를 써보신 분은 알 C&C에서는 유닛들이 부품들로 이루어져 있고, 스크립 통해 부품 조합을 새로 함으로써 MOD를 만들 수 있도록 해놓았습니다. 탱크같으면 포는 레이저포, 장갑은 특수 티타늄 장갑, 이동장치는 탱크바퀴 등등 이런 식으로 객체가 나뉘어져 있습니다. 이런 것을 구현할 때에도 이 abstract fac 패턴은 아주 유용합니다. 인터페이스를 통일해 버리기 때문에 프로그래밍 부분에서는 그 부품이 어떤 것인지는 신 경을 쓸 필요가 없습니다. 단지 인터페이스를 갖고 놀면서 프로그래밍을 하면 되는 것이지요. 반면에 이 패턴을 쓰지 않으면 if문을 남발한 하드 코딩을 하게 될 것입니다. 더 좋은 방법을 생각해 낸다 하더라도 이 패턴을 이용하는 것만 많은 이점을 가지기는 힘들 겠지요. 잘 쓰시기 바랍니다.


State패턴을 하기 전에 함수 포인터의 대안으로서 가상함수를 소개해 드리겠습니다. State패턴이 사실 함수포인터랑 비슷하기 때문에...

< 가상함수를 이용한 함수포인터 >
가끔 너무 많은 if문 때문에 함수 포인터를 쓰는 경우가 있을 것입니다. 이 가상함수는 함수 포인터와 비슷하다고 할 수 있습니다. 차이점이라면 더 유연하고 직관적이며... 암튼 더 좋습니다. 

void HandleCommand( int CommandID )
{
switch( CommandID )
{
case 1: 
case 2: 
case 3:

default:
}
}

이것을 함수 포인터로 바꾸면...

void Command1() { //구현.... }
void Command2() { }
void Command3() { }

typedef void (*CommandFunc)();
CommandFunc Commands[3] = { Command1, Command2, Command3 };

void HandleCommand( int CommandID )
{
Commands[ CommandID ]();
}

대충 이렇게 되겠죠.. (맞나...?) 이것을 가상함수 버전으로 바꾸면....

class iCommand
{
public:
virtual void Operate() = 0;
};

class cCommand1 : public iCommand
{
public:
void Operate() {}
};

class cCommand2 : public iCommand
{
public:
void Operate() {}
};

class cCommand3 : public iCommand
{
public:
void Operate() {}
};

cCommand1 com1;
cCommand2 com2;
cCommand3 com3;
iCommand * Commands[3] = { &com1, &com2, &com3 };

void HandleCommand( int CommandID )
{
Commands[ CommandID ]->Operate();
}

이렇게 됩니다. 사실 위와 같은 경우는 함수포인터보다 딱히 좋다고 할 수가 없습니다. 그럼 어느 경우에 좋은가? 

- 다양한 인자를 받을 수 있다.
함수포인터는 문법 특성상 인자를 통일시켜야 하지만 가상함수를 이용한 방법은 다양한 인자를 넘겨줄 수 있습니다.
이에 대한 예제는 State 패턴에 나옵니다. 
- 둘 이상의 함수도 지정할 수 있다.
불가능하죠.
- 상속과 재정의를 이용한 확장이 용이하다.
이런 여러가지 장점이 있습니다. 클래스가 갖는 장점은 모두 가지고 있다고 보면 되겠습니다.

그리고 성능상의 장점이 있는데, switch문을 이용한 방법보다는 빠르고 함수포인터와는 비슷한 속도를 내게 될 것입니다.

Posted by Junios

2009/02/26 18:00 2009/02/26 18:00
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/235


블로그 이미지

Junios World

- Junios

Archives

Authors

  1. Junios

Calendar

«   2010/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
33027
Today:
29
Yesterday:
108