지난 시간 이야기에서 한가지 의아한 점이 떠오르는 잉여도 있을지 모르겠다.
(그렇다, 사실 의문점이 떠오르면 이미 잉여를 탈티한 것일지도!)
머냐면 MFC의 CCmdTarget같은 클래스가 없었다는 것이다.
CCmdTarget를 뺀 이유는 이 클래스 설계 이야기는 일단 논리적인 설계 이야기이기 때문이다.
CCmdTarget같이 메시지를 처리하는 클래스가 들어간다는 것은 UI가 붙는다는 이야기이며
메시지 처리와 UI가 붙게 되면 이건 논리적인 클래스 설계 이야기가 아니라 엔진 이야기로 흘러간다.
그것도 범용 엔진인지, 엔진과 게임 컨텐츠를 합쳤는지에 따라 복잡도가 나뉘며
(당연히 범용 엔진이 훨~~~~얼~~~~씬 더 크다. 범용 윈도우 라이브러리인 MFC를 생각해보라.)
대강 구현해도 클래스랑 인터페이스 수십개는 마구마구 쏟아져 나온다.
그만큼 실제 게임 프로젝트와 학교나 학원에서 배우는 OOP는 실로 차이가 크다고도 할수 있겠다.
(그렇다, 사실 의문점이 떠오르면 이미 잉여를 탈티한 것일지도!)
머냐면 MFC의 CCmdTarget같은 클래스가 없었다는 것이다.
CCmdTarget를 뺀 이유는 이 클래스 설계 이야기는 일단 논리적인 설계 이야기이기 때문이다.
CCmdTarget같이 메시지를 처리하는 클래스가 들어간다는 것은 UI가 붙는다는 이야기이며
메시지 처리와 UI가 붙게 되면 이건 논리적인 클래스 설계 이야기가 아니라 엔진 이야기로 흘러간다.
그것도 범용 엔진인지, 엔진과 게임 컨텐츠를 합쳤는지에 따라 복잡도가 나뉘며
(당연히 범용 엔진이 훨~~~~얼~~~~씬 더 크다. 범용 윈도우 라이브러리인 MFC를 생각해보라.)
대강 구현해도 클래스랑 인터페이스 수십개는 마구마구 쏟아져 나온다.
그만큼 실제 게임 프로젝트와 학교나 학원에서 배우는 OOP는 실로 차이가 크다고도 할수 있겠다.
머 어찌됐던, 3번째 이야기로 들어가보자.
다만 이제부터는 디아블로2와 게임 컨텐츠가 좀 다르다고 까진 말아주세욘. ^^
원래 디아블로2 클래스 만들기가 절대 아니었고 예로써 디아블로2를 든 것일 뿐이니까 말이져~
원래 디아블로2 클래스 만들기가 절대 아니었고 예로써 디아블로2를 든 것일 뿐이니까 말이져~
게임에는 아이템이 있다. 그걸로 밥먹고 사는 우리들로썬 아이템의 중요성은 설명하지 않아도 될 것이다.
그리고 게임에는 비단 마법사 뿐만 아니라 적 캐릭터를 포함한 모든 직업 클래스에 마법속성이 부여된다.
처음부터 특정 캐릭터 클래스는 무슨 속성이라고 정해줄수도 있으며.
모든 속성(파이어, 아이스, 대지, 공기, 정령계....)들을 수치로 가지고 있고 그 속성들 중 특정 속성이 상대적으로 높고 낮냐에 따라 결정될 수도 있겠다.
비단 캐릭터 뿐이겠는가?
아이템에도 속성이 있고 장비와 무기에도 속성이 있다.
예컨데 노템(홀랑 벗었을)일 경우, 파이어 속성을 지닌 법사가
얼음 속성을 지닌 옷과 무기 및 장비들을 한가득 입고 나서 아이스계 법사가 될수도 있을 것이다.
머, 그럴 경우 원래 장비속성 빼기 캐릭터 속성 어쩌고 해서 공식이 만들어지겠지만
그런건 시스템 기획자의 보구인 엑셀로 알아서 잘해줄 것이다.
얼음 속성을 지닌 옷과 무기 및 장비들을 한가득 입고 나서 아이스계 법사가 될수도 있을 것이다.
머, 그럴 경우 원래 장비속성 빼기 캐릭터 속성 어쩌고 해서 공식이 만들어지겠지만
그런건 시스템 기획자의 보구인 엑셀로 알아서 잘해줄 것이다.
결론적으로 이 게임은 모든 직업 클래스와 무기 및 아이템 클래스는 속성을 갖고 있어야 한다라는 대명제가 생겼다.
따라서 개발자인 우리는 지난 시간의 클래스 구조도를 대폭 손질해야 한다.
어떤 방법이 있겠는가?
가장 쉬운 방법은 지난 시간에 없었던 아이템 클래스를 먼저 추가시키고,
모든 CGObject 아래에 속성값을 지니는 클래스를 끼워넣는 것이다.
먼저 아이템을 넣자.
아이템은 캐릭터와 함수와 변수를 함께 써 먹을 일이 없다. 따라서 지금까지의 상속관계와 무관하다.
흠... 일단 아이템을 넣었다.
이제 위에서 언급한 대로 CGObject 밑에 속성 클래스를 두자.
이것으로 모든 캐릭터와 아이템은 속성을 지니게 되었다! 와~~~!! 행복하다!!!
이번 강좌는 이것으로 끝!!!!
...이면 얼마나 좋을까?
세상은 이렇게 단순하지가 않다.
그러나 이 복잡한 세상을 단순화 시켜주는 클래스 설계 공식이 있으니 이른바 IS-A 관계와 HAS-A 관계가 그것이다.
먼저 IS-A란 클래스 설계시 상속이 적절한가를 판단할 때 사용하는 것으로 "파생 클래스는 슈퍼 클래스이다" 라는 논리가 파당하다면 상속해도 된다는 의미이다. IS-A는 지난 시간 만든 클래스에 딱 맞아떨어지는데, 모든 클래스를 두고 한번 이 공식에 넣어보자 생각해보자.
( ) 클래스는 ( ) 클래스이다.
여기에 대입해보면,
아마존은 게임 캐릭터이다.
상인은 게임 캐릭터이다.
NCP는 캐릭터이다.
어세신은 객체이다.
......
상인은 게임 캐릭터이다.
NCP는 캐릭터이다.
어세신은 객체이다.
......
등의 관계가 적절함을 알 수 있다.
지난 시간, 잘못된 상속의 사례였던 CAssAmamzon을 생각해보자.
CAssAmamzon는 CAssassin과 CAmazon을 다중상속받았다.
어세신-아마존은 어세신이다.
어세신-아마존은 아마존이다.
어세신-아마존은 아마존이다.
흠... 이러한 관계는 적절치 못하다. 어세신-아마존은 결코 어세신도 아니며 아마존도 아닌 제 3의 클래스이기 때문이다.
얼마 전, 어떤 잉여의 상속 설계 숙제에서 전형적으로 실수한 부분을 본적이 있는데,
육식동물과 초식동물을 다중상속받은 잡식동물 클래스를 본적이 있다.
육식동물과 초식동물의 속성과 메서드를 한꺼번에 상속받으면 그럴싸한 것 같지만 절대 상속해선 안되는 좋은 사례가 된다.
특히 앞으로 해볼 다형성(Polymorphism, 폴리모피즘) 구현에서 치명적인 실수가 드러나게 되며 절대 개발할수 없는 설계로 가는 지름길이다. 문제는 많은 좆뉴비들과 잉여들이 자신도 모르게 이렇게 해버리는 경향이 많은데, 프로그래밍 이전에 논리력이 의심이 된다. 찰스 다윈의 종의 기원이나 철학책을 읽으며 기본적인 논리공부를 병행하길 바란다.
아뭏든 앞으로 상속할지 여부는 이 IS-A 공식에 대입해보면 된다.
그래서 논리가 타당하면 상속해도 되며, 논리가 맞지 않다는 의심이 들면 상속을 피하길 바란다.
그럼 오늘의 클래스 설계는 어떤가?
상기대로라면 실제 인스턴스 생성시 "아마존은 파이어이다.", "양손검은 아이스이다."라는 논리가 생겨버리게 된다.
당연히 논리에 맞지 않다!!
정확한 논리는 "아마존은 파이어속성을 갖고 있다.", "양손검은 아이스 속성을 갖고 있다."라고 불려야 한다. 이것이 바로 HAS-A 관계이다. 즉, 속성은 상속의 대상이 아니라 속해있는 대상이므로 하나의 클래스가 다른 클래스를 상속받는 것이 아닌 포함의 관계를 만들어야 한다. 물론 이것은 일반적인 클래스 상속으로도 구현할 수는 있다. 그러나 전형적인 결합도(coupling) 상승의 원인이 되므로 피하는 것이 좋다.
"소서리스는 아이스속성을 갖고 있다"라는 명제를 나타내보자.
귀찮아서 아이스는 int 100 이다. =_=;;
#include <iostream>
using namespace std;
//............
class CAttribute : CGObject
{
public:
int GetCurrentAttribute() { return 100; } // 아 귀찮...ㅠㅠ
};
class CSorceress: public CGameCharacter
{
public:
virtual void foo() = 0;
CAttribute* m_Attribute; // 포함
};
class CPlayerSorceress : public CPlayer, public CSorceress
{
public:
CPlayerSorceress()
{
m_Attribute = new CAttribute; // CSorceress 에서 멤버로 갖고 있다. 포함의 위치는 각자 알아서 구현하자.
}
~CPlayerSorceress()
{
delete m_Attribute;
}
void foo() {}
int GetAttribute()
{
return m_Attribute->GetCurrentAttribute();
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CPlayerSorceress Sorc;
cout << Sorc.GetAttribute() << endl;
return 0;
}
using namespace std;
//............
class CAttribute : CGObject
{
public:
int GetCurrentAttribute() { return 100; } // 아 귀찮...ㅠㅠ
};
class CSorceress: public CGameCharacter
{
public:
virtual void foo() = 0;
CAttribute* m_Attribute; // 포함
};
class CPlayerSorceress : public CPlayer, public CSorceress
{
public:
CPlayerSorceress()
{
m_Attribute = new CAttribute; // CSorceress 에서 멤버로 갖고 있다. 포함의 위치는 각자 알아서 구현하자.
}
~CPlayerSorceress()
{
delete m_Attribute;
}
void foo() {}
int GetAttribute()
{
return m_Attribute->GetCurrentAttribute();
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CPlayerSorceress Sorc;
cout << Sorc.GetAttribute() << endl;
return 0;
}
CAttribute에 대한 물리적인 상속구현은 없었지만 멤버로 유효하며 그 결과 역시 잘 나온다.
아쉽게도 이제까지 써온 VS2008의 다이어그램 기능은 C++에서 포함을 나타내주진 못하고 있기에 다이어그램은 생략한다.
(되는 방법이 있으면 알려주세요.)
여기에 좀더 생각해볼 것은, COM에서는 애초에 다중상속을 지원하지 않는다는 사실이며 이와 같은 방법으로 구현한다는 점이다.
그 천재들이 만든 COM이 왜 다중상속을 지원하지 않으며 굳이 포함과 통합이란 방법을 쓰는지 생각해볼 일이다.
다시 정리하자면 IS-A로 나타낼수 있으면 그 상속은 타당하다.
그리고 HAS-A로 나타나면 물리적인 상속 대신 논리적인 상속, 즉 해당 객체를 멤버로 가지도록 하자.
오늘의 탐구생활~
1) CItem과 CWeapon에서 상속받은 실제 아이템과 무기 클래스를 만들자.
2) 혹시 누군가 "이 아이템은 땅에 버리게 되면, 지가 알아서 막 공격도 하고 도망가도록 해주세요~"라고 했을땐 어떻게 해야할지도 고민해보자.
3) 이제까지 CPlayer, CEnemy, CNPC는 CCharacter에서 상속을 받았다. 오늘 포스트에 기준해 이것들의 각각의 상속이 논리적으로 타당한지에 대해 치고 박고 싸워보자.
마지막으로 잉여에게 뇌세포가 딸릴 것이 분명한 고급 탐구생활!!!
4) 실제 CPlayerAmazon의 인스턴스는 무기와 아이템을 "가진다".
그렇다면 무기와 아이템을 CPlayerAmazon의 멤버로 포함시키면 어떨까?
그리고 실제 데미지를 주는 공격함수는 무기와 아이템들이 가질 것인가? 아니면 CPlayerXXXX가 가질 것인가?
이 문제에 대해 깊이있는 OOP에 대한 짱구를 돌려 보자. 뜬금없게도 이것은 나중에 게임 속 인공지능에 대한 기초가 된다.
1) CItem과 CWeapon에서 상속받은 실제 아이템과 무기 클래스를 만들자.
2) 혹시 누군가 "이 아이템은 땅에 버리게 되면, 지가 알아서 막 공격도 하고 도망가도록 해주세요~"라고 했을땐 어떻게 해야할지도 고민해보자.
3) 이제까지 CPlayer, CEnemy, CNPC는 CCharacter에서 상속을 받았다. 오늘 포스트에 기준해 이것들의 각각의 상속이 논리적으로 타당한지에 대해 치고 박고 싸워보자.
마지막으로 잉여에게 뇌세포가 딸릴 것이 분명한 고급 탐구생활!!!
4) 실제 CPlayerAmazon의 인스턴스는 무기와 아이템을 "가진다".
그렇다면 무기와 아이템을 CPlayerAmazon의 멤버로 포함시키면 어떨까?
그리고 실제 데미지를 주는 공격함수는 무기와 아이템들이 가질 것인가? 아니면 CPlayerXXXX가 가질 것인가?
이 문제에 대해 깊이있는 OOP에 대한 짱구를 돌려 보자. 뜬금없게도 이것은 나중에 게임 속 인공지능에 대한 기초가 된다.
자, 그럼 대망의 마지막회에서는 은닉(캡슐화)과 다형성에 대해 적어보게따~~*
