프로토타입 디자인 패턴
건틀릿 같은 게임을 만든다고 해보았으 ㄹ때,
몬스터들은 영웅을 잡아먹기 위해 떼지어 다닌다.
영웅을 저녁 식사 삼으려는 스포너를 통해 게임 레벨이 등장하는데, 몬스터 종류마다 스포너가 따로 있다.
예제용으로 게임에 나오는 몬스터마다 Ghost, Demon, Sorcerer 같은 클래스를 만들어보자.
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 DemonSpawner : public Spawner {
public :
virtual Monster* spawnMonster()
{
return new Demon();
}
}
코드를 많이 작성할 수록 돈을 더 받는다면 모를까,
이 코드는 영 별로이다. 클래스도 많고 행사 코드도 많고 반복 코드도 많다.
중복도 많고 "많다"라는 말도 많이 들어간다.
이런 걸 프로토타입 패턴으로 해결할 수 있다.
핵심은 어떤 객체가 자기와 비슷한 객체를 스폰할 수 있다는 점이다.
유령 객체 하나로 다른 유령 객체 여럿을 만들 수 있다.
악마 객체 하나로부터도 다른 악마 객체를 만들 수 있다.
어떤 몬스터 객체든지 자신과 비슷한 몬스터 객체를 만드는 원형 객체로 사용할 수 있다.
이를 구현하기 위해, 상위 클래스인 Monster에 추상 메서드 clone()을 추가한다.
class Monster {
public :
virtual ~Monster() {}
virtual Monster* clone() = 0;
// 그 외...
}
Monster 하위 클래스에서는 자신과 자료형과 같은 새로운 객체를 반환하도록 clone()을 구현한다.
예를 들어 유령 객체라면 다음과 같다.
class Ghost : public Monster {
public :
Ghost(int health, int speed)
: heakth_(health), speed_(speed)
{
}
virtual Monster* clone {
return new Ghost(health_, speed_);
}
private
int health_;
int speed_;
};
Monster를 상속받는 모든 클래스에 clone 메서드가 있다면,
스포너 클래스를 종류별로 만들 필요 없이 하나만 만들면 된다.
class Spawner {
public :
Spawner(Monster* prototype) : prototype_(prototype) {}
Monster* spawnMonster() {
return prototype_->clone();
}
private:
Monster* prototype_;
};
Spawner 클래스 내부에는 Monster 객체가 숨어 있다.
이 객체는 벌집을 떠나지 않는 여왕벌처럼 자기와 같은 Monster 객체를 도장 찍듯 만들어내는 스포너 역할만 한다.
유령 스포너를 만들려면 원형으로 사용할 유령 인스턴스를 만든 후 에 스포너에 전달한다.
Monster* ghostPrototype = new Ghost(15,3);
Spawner* ghostSpawner = new Spawner(ghostPrototype);
프로토타입 패턴의 좋은 점은 프로토타입의 클래스뿐만 아니라 상태도 같이 복제한다는 점이다.
즉, 원형으로 사용할 유령 객체를 잘 설정하면 빠른 유령, 약한 유령, 느린 유령용 스포너 같은 것도 쉽게 만들 수 있다.
프로토타입 패턴은 우아하면서도 놀랍다.
또한 너무 간단하기 때문에 따로 외우려 노력할 필요가 없다.
얼마나 잘 작동하는가?
이제 몬스터마다 스포너 클래스를 따로 만들지 않아도 된다.
그래도 Monster 클래스마다 clone()을 구현해야 하기 때문에 코드 양은 별반 차이가 없다.
clone()를 만들다보면 애매할 때도 있다.
객체를 깊은 복사를 해야 할까, 얕은 복사를 해야 할까?
악마가 삼지창을 들고 있다면, 복제된 악마도 삼지창을 들고 있어야 할 까?
앞에서 봤듯이 프로토타입 패턴을 써도 코드 야잉 많이 줄어들지 않는 데다가,
예제부터가 현실적이지 않다.
요즘 나오는 웬만한 게임 엔진에서 몬스터마다 클래스를 따로 만들지 않는다.
우리 프로그래머들은 오랜 삽질을 통해서 클래스 상속 구조가 복잡하면 유지보수하기 힘들다는 걸 체득했다.
따라서 요즘은 개체 종류별로 클래스를 만들기보단 컴포넌트나 타입 객체로 모델링하는 것을 선호한다.
스폰 함수
앞에서 모든 몬스터마다 별도의 스포너 클래스가 필요했다. 하지만 모든 일에는 답이 여러개 있는 법이다.
다음과 같이 스폰 함수를 만들어보자.
Monster* spawnGhost()
{
return new Ghost();
}
몬스터 종류마다 클래스를 만드는 것보다는 행사코드가 훨씬 적다.
이제 스포너 클래스에는 함수 포인터 하나만 두면 된다.
typedef Monster* (*SpawnCallback)();
class Spawner {
public:
Spawner (SpawnCallback spawn) : spawn_(spawn) {}
Monster* spawnMonster() {reutrn spawn_();}
private:
SpawnCallback spawn_;
};
유령을 스폰하는 객체는 이렇게 만들 수 있다.
Spawner* ghostSpawner = new Spawner(spawnGhost);
템플릿
요즘 C++ 프로그래머는 다들 템플릿을 잘 안다.
스포너 클래스를 이용해 인스턴스를 생성하고 싶지만 특정 몬스터 클래스를 하드코딩하기는 싫다면
몬스터 클래스를 템플릿 타입 매개변수로 전달하면 된다.
class Spawner {
public:
virtual ~Spawner() {}
virtual Monster* spawnMonster() = 0;
};
templater <class T>
class SpawnerFor : pulbic Spawner {
public:
virtual Monster* spawnMonster() {return new T();}
};
템플릿으로 만들면 사용법은 다음과 같다.
Spawner* ghostSpawner = new SpawnerFor<Ghost>();
일급 자료형
앞에서 본 두 방법을 통해서 Spawner 클래스에서 자료형을 매개변수로 전달할 수 있다.
C++에서는 자료형이 일급 자료형이 아니다 보니 이런 곡예를 해야 한다.
JS, Python, Ruby 등 클래스가 전달 가능한 일급 자료형인 동적 자료형 언어에서는
이 문제를 훨씬 직접적으로 풀 수 있다.
스포너를 만들기 위해서는 원하는 몬스터 클래스를, 그것도 실제 런타임 객체를 그냥 전달하면 된다.
식은 죽 먹기다.
++ 일급 자료형이란??
일급 자료형(일급 객체, First-class object)은 프로그래밍 언어에서 다른 객체들과 동등하게 취급될 수 있는 자료형을 의미한다.
+++++
이런 여러 가지 선택에도 불구하고, 솔직히 말하자면 프로토타입 디자인 패턴이 언제 가장 이상적인지는 모른다.
지금부터는 디자인 패턴으로서의 프로토 타입이 아닌,
언어 패러다임으로서의 프로토타입에 대해 생각해보자.
프로토타입 언어 패러다임
'객체지향 프로그래밍'이 곧 '클래스'라고 많이들 생각한다.
갈라진 여러 종교 종파처럼 사람들은 OOP를 다르게 정의하지만,
OOP가 데이터와 코드를 묶어주는 객체를 직접 정의할 수 있게 한다는 점만큼은 대부분 동의한다.
C 같은 구조형 언어나 스킴 같은 함수형 언어와 비교한다면 OOP의 가장 튼 특징은 상태와 동작을 함게 묶는 데 있다.
클래스만이 이를 위한 유일한 방법일 거라고 생각할지 모르나,데이비드 엉거, 랜덜 스미스를 포함한 몇몇은 달랐다.
이는 셀프라는 언어를 1980년대에 만들었다.셀프에서는 OOP에서 할 수 있는 걸 다 할 수 있지만 클래스는 없다.
셀프
순수하게 의미만 놓고 본다면 셀프는 클래스 기반 언어보다 더 객체지향적인 언어이다.상태와 동작을 같이 묶어놓은 것을 OOP라고 할 때 클래스 기반 언어는 상태와 동작 사이에 분명한 구별이 있다.
친숙한 클래스 기반 언어를 하나 떠올려보자.객체 상태를 알기 위해서는 해당 인스턴스 메모리를 들여다봐야한다.즉, 상태는 인스턴스에 들어 있다.
반대로 메서드를 호출할 때는 인스턴스의 클래스를 찾는다.즉, 동작은 클래스에 있다. 항상 한 단계를 거쳐서 메서드를 호출한다는 점에서 필드 (상태)와 메서드는 다르다.

셀프는 이런 구별이 없다.
무엇이든 객체에서 바로 찾을 수 있다.
인스턴스는 상태와 동작 둘 다 가질 수 있다.
유이무이한 메서드를 가진 개겣로도 만들 수 있다.
이게 셀프의 전부라면 그다지 유용해보이지 않았을 것이다.
클래스 기반 언어에서 상속은 나름의 단점도 있지만,
다형성을 통해서 코드를 재사용하고 중복 코드를 줄일 수 있다는 장점이 있다.
클래스 없이 이러한 일을 수행하기 위해 셀프에는 위임 개념이 있다.
먼저 해당 객체에서 필드나 메서드를 찾아본다.
있다면 그걸 쓰고, 없다면 상위 객체를 찾아본다.
상위 객체는 그냥 다른 객체 레퍼런스일 뿐이다.
첫 번재 객체에 속성이 없다면 사우이 객체를 살펴보고,
그래도 없다면 상위 객체의 사우이 객체에서 찾아보고, 이를 반복한다.
다시 말해 찾아보고 없으면 상위 객체에 위임한다.

상위 객체를 통해서 동작(과 상태)을 여러 객체가 재사용할 수 있기 때문에 클래스가 제공하는
기능 대부분을 대신할 수 있는 것이다.
클래스의 또 다른 역할은 인스턴스 생성이다.
언어마다 문법은 조금씩 다르겠지만, 대부분 new Thingamabob() 식으로 Thingamabob을 새로 만들 수 있다.
클래스는 자기 자신의 인스턴스 생성기이다.
클래스가 없다면 어떤 식으로 객체를 만들 수 있을까?
특히 내용이 같은 객체를 많이 만들면 어떻게 해야 할까?
셀프에서는 프로토타입 패턴에서 본 것 처럼 복제하면 된다.
셀프에서는 모든 개겣가 프로토타입 디자인 패턴을 저절로 지원하는 것과 다를 게 없다.
모든 객체가 복제될 수 있기 때문에 비슷한 객체를 여럿 만들면 다음과 같이한다.
(=즉, 프로토 타입이란,
인스턴스로 생성할 수 있도록 하는 객체를 복제하는 것이다.
이러한 복제 객체 타입을 소유하는 것을
프로토 타입 디자인 패턴이라고 이해했다.)
1. 객체 하나를 원하는 상태로 만든다.
시스템에서 제공하는 기본 Object 객체를 복제한 뒤에 필드와 메서드를 채워 넣는다.
2. 원하는 만큼 복제한다.
셀프에서는 귀찮게 직접 clone 메서드를 구현하지 않아도 프로토타입 디자인 패턴의 우아함을 시스템적으로 제공한다.
나는 셀프의 아름답고 똑똑하며 가벼운 시스템에 반해서
프로토타입 기반 언어를 더 알고 싶은 마음에 언어를 직접 만들기 시작했다.
그래서 어떻게 됐을까?
처음 순수 프로토타입 기반 언어로 가지고 놀 때에는 굉장히 신났지만,
하다 보니 프로토타입 기반으로 프로그래밍하는 게 별로 재미없다는 슬픈 사실을 알게 되었다.
분명 언어를 구현하기는 쉬웠지만 그게 다 복잡한 걸 사용자에게 떠넘겼기 때문이다.
직접 써 봤더니 금방 클래스가 제공하는 구조가 아쉬워져서 결국에는 언어에서 제공하지 않는 기능을
라이브러리단에서 다시 만들어야 했다.
자바스크립트는 어떤가?
정말 프로토타입 기반 언어가 접근성이 낮다면,
자바스크립트는 어떻게 된 것 일까?
프로토타입 기반이지만, 수많은 사람들이 매일 사용하는, 언어인데 말이다..
js를 만든 브랜던 아이크는 셀프로부터 직접 영감을 받았다.
많은 js 문법이 프로토타입 기반 방식이다.
객체는 아무 속성 값이나 가질 수 있는데, 속성에는 드나 메서드’(실제로는 필드로서 저장된 함수)가 들어간다.
객체는 ‘프로토타입’이라고 부르는 다른 객체 지정할수있어서,
자기 자신에 없는필드는 프로토타입에 위임할 수 있다.
그렇긴 해도 실제로 자바스크립트는 프로토타입 기반 언어보다 래스 기반 언어에 더 가깝다 고생각한다.
자바스크립트에는 프로토타입 기반 언어의 핵심인 복제를 찾아볼 수 없다는게 셀프와의 차이점을 잘 보여준다.
'컴퓨터 프로그래밍 공부 > 디자인 패턴' 카테고리의 다른 글
| 게임 프로그래밍 패턴 - 게임 루프 (8) | 2025.08.17 |
|---|---|
| 이중 패턴 (3) | 2025.08.10 |
| 게임 프로그래밍 패턴 1장 정리 (3) | 2025.07.20 |