[게임 프로그래밍 패턴] Chapter5 프로토타입

Jangmanbo·2023년 8월 22일
0

프로토타입의 개념 및 디자인 패턴으로서의 프로토타입에 대해 배우는 챕터

프로토타입 디자인 패턴

몬스터에는 여러 종류가 있다.

class Monster { ... };
class Ghost : public Monster {};
class Demon : public Monster {};
class Sorcerer : public Monster {};

몬스터마다 클래스를 만들었으니, 스포너도 몬스터 클래스처럼 종류마다 만들어보자

class Spawner {
public:
	virtual ~Spawner() {}
    virtual Monster* spawnMonster() = 0;
};

class GhostSpawner : public Spawner {
public:
	virtual Monster* spawnMonster() { return new Ghost(); }
};

...

이런식으로 구현하면 행사코드, 반복코드가 많아진다...

행사코드 : 프로그램의 실행과는 직접적으로 관계 없는 프로그래밍 문법적 서식을 의미


프로토타입 패턴 적용

class Monster {
public:
	virtual ~Monster() {}
    virtual Monster* clone() = 0;
    
    // ...
};

class Ghost : public Monster {
public:
	Ghost(int health, int speed) : health_(health), speed_(speed) {}
    virtual Monster* clone() { return new Ghost(health_, speed_); }

private:
	int health_;
    int speed_;
};

순수가상함수 clone을 선언하고 Monster 하위클래스에서 자신의 자료형과 상태가 같은 새로운 객체를 반환하도록 clone을 구현한다.

class Spawner {
public:
	Spawner(Monster* prototype) : prototype_(prototype) {}
    Monster* spawnMonster() {
    	return prototype_->clone();
    }
private:
	Monster* prototype_;
};

// 유령 스포너 만들기
Monster* ghostPrototype = new Ghost(15, 3);
Spawner* ghostSpawner = new Spawner(ghostPrototype);

Spawner 클래스의 Monster 객체는 자신과 똑같은 Monster객체를 만들어내는 스포너 역할을 한다.
이렇게 하면 몬스터 종류마다 스포너 클래스를 만들지 않아도 된다.

문제점

  • 여전히 Monster 클래스마다 clone()을 구현해야 한다.
  • clone()에서 객체를 깊은 복사할지, 얕은 복사할지...
  • 요즘은 몬스터마다 클래스를 만들지 않는다.
    • 컴포넌트(챕터14), 타입객체(챕터13) 참고

스폰 함수

Monster* spawnGhost() { return new Ghost(); }

유령 몬스터를 스폰하는 함수를 만들었다.

typedef Monster* (*SpanwCallBack)();

class Spawner {
public:
	Spawner(SpawnCallback spawn) : spawn_(spawn) {}
    Monster* spawnMonster() { return spawn_(); }
private:
	SpawnCallBack spawn_;
};

// 유령 스포너 만들기
Spawner* ghostSpawner = new Spawner(spawnGhost);

하나의 스포너 클래스로 여러 종류의 몬스터 스포너를 만들 수 있으면서 위의 문제점 1,2번도 해결 가능하다.

스폰함수는 어디서 선언하지?

템플릿

몬스터 종류마다 스포너 클래스를 하드코딩하기 싫다면 몬스터 클래스를 템플릿 타입 매개변수로 전달하자.

class Spawner {
public:
	virtual ~Spawner() {}
    virtual Monster* spawnMonster() = 0;
};

template <class T>
class SpawnerFor : public Spawner {
public:
	virtual Monster* SpawnMonster() { return new T(); }
};

// 유령 스포너 만들기
Spawner* ghostSpawner = new SpawnerFor<Ghost>();

함수 포인터에 스폰함수까지 만드는 것보다는 템플릿이 아무리 봐도 더 좋아보이는데..
템플릿에 비해 스폰함수로 자료형을 넘길 때의 장점이 뭘까?


일급 자료형

이렇게 스폰함수나 템플릿을 통해 Spawner 클래스에 자료형을 매개변수로 전달할 수 있다.

자바스크립트, 파이썬 등과 달리 이런 번거로운 방식으로 Spawner 클래스에 자료형을 매개변수로 전달하는 것은 C++에서 자료형은 일급 객체가 아니기 때문이다. (자료형 그 자체를 매개변수로 전달할 수 없다.)


프로토타입 언어 패러다임

OOP의 가장 큰 특징은 데이터와 코드, 즉 상태와 동작을 묶어 객체를 직접 정의할 수 있다는 것이다.
이는 클래스를 통해서만 가능할거라 생각하겠지만, 셀프라는 언어에서는 클래스가 없어도 상태와 동작을 묶을 수 있다.

클래스 기반 언어-셀프(프로토타입 기반 언어) 차이점

1. 필드와 메서드의 위치

클래스 기반 언어에서 객체 상태는 인스턴스에, 동작은 클래스에 있다.
그러나 셀프는 상태든 동작이든 객체에서 바로 찾을 수 있다. 즉 인스턴스가 상태와 동작 둘다 가질 수 있다.
또한 유일무이한 메서드를 가진 객체도 만들 수 있다.

2. 위임

클래스 기반 언어에서는 다형성을 통해 코드를 재사용하는데, 셀프에서는 위임으로 중복 코드를 줄인다.

  • 해당 객체에서 필드나 메서드를 찾는다.
  • 있으면 그걸 쓰고 없다면 상위 객체에서 찾는다.
  • 없으면 상위 객체의 상위 객체를 찾는다. 이를 반복한다. => 상위 객체에게 위임한다.

3. 복제

클래스 기반 언어는 클래스로 인스턴스를 생성한다. ex. new Thingamabob()
셀프에서는 프로토타입 패턴에서처럼 자신을 복제하여 객체를 생성한다. 즉, 셀프에서는 모든 객체가 프로토타입 디자인 패턴을 지원하다고 보면 된다.


프로토타입 기반 언어의 한계

일반적인 게임에서는 캐릭터 클래스, 스킬 종류 등을 명확하게 나누고 있다. 예로 트롤과 고블린을 적당히 섞는다거나 하는 경우는 보기 드물다. 따라서 확실하게 나누고 묶어주는 클래스 기반 언어를 더 편하게 받아들이게 된다.

결론: 프로토타입 기반 언어는 접근성이 낮다!


자바스크립트

그렇다면 대표적인 프로토타입 기반 언어인 자바스크립트는 어떻게 된걸까...

자바스크립트의 객체는 아무 속성(필드, 메서드)이나 가질 수 있다.
객체는 프로토타입이라 부르는 다른 객체를 지정하여 자신에 없는 필드(+메서드)느느 프로토타입에 위임할 수 있다. (앞에서 말한 1, 2번 만족)
그러나 프로토타입 기반 언어의 핵심인 복제는 지원하지 않는다.

때문에 자바스크립트는 클래스 기반 언어에 가깝다고 볼 수 있다.

자바스크립트 특징

  • 자료형을 정의하는 객체로부터 new를 호출하는 생성자 함수를 통해 겍체를 생성
  • 상태는 인스턴스에 저장
  • 동작은 자료형이 같은 객체가 모두 공유하는 메서드 집합을 대표혀는 별도 객체인 프로토타입에 저장되고 위임을 통해 간접 접근

예시는 책 보기..


데이터 모델링을 위한 프로토타입

세월이 지날수록 코드보다 데이터가 차지하는 용량이 커지고 있다.
코드는 함수, 클래스 상속, 믹스인 등으로 중복 코드를 줄여 용량을 줄인다.

게임 데이터도 이와 같은 기능이 필요하다.

{
	"이름": "고블린 보병",
  	"기본체력:" 20,
  	"내성": ["추위", "독"]
}

{
	"이름": "고블린 마법사",
  	"기본체력:" 20,
  	"내성": ["추위", "독"],
	"마법": ["화염구", "번개 화살"]
}

{
	"이름": "고블린 궁수",
  	"기본체력:" 20,
  	"내성": ["추위", "독"],
	"공격방법": ["단궁"]
}

JSON으로 고블린 데이터를 만들어보았다. 중복이 많으므로 프로토타입 패턴을 이용해 개선해보자

프로토타입 패턴 적용

{
	"이름": "고블린 보병",
  	"기본체력:" 20,
  	"내성": ["추위", "독"]
}

{
	"이름": "고블린 마법사",
  	"프로토타입": "고블린 보병",
	"마법": ["화염구", "번개 화살"]
}

{
	"이름": "고블린 궁수",
  	"프로토타입": "고블린 보병",
	"공격방법": ["단궁"]
}

데이터 모델에 프로토타입을 지정하여 고블린 보병의 필드를 반복해서 입력하지 않게 개선했다.

여기서 포인트는 '기본 고블린' 같은 추상 프로토타입이 아니라 가장 단순한 고블린 자료형 하나를 골라서 위임한 것이다.
이는 프로토타입 기반 시스템에서는 어떤 객체든 복제로 사용할 수 있어야 하기 때문이다.

0개의 댓글