“使用特定原型实例来创建特定种类的对象,并且通过拷贝原型来创建新的对象。”
原型设计模式
我们游戏里面有3种怪物类型——幽灵、恶魔和术士。
原型模式提供了一种解决方案,其核心思想是一个对象可以生成与自身相似的其他对象。如果你有一个幽灵,则你可以通过这个幽灵制作出更多的幽灵。如果你有一个魔鬼,那你就能制作出其他魔鬼。任何怪物都能被看作是一个原型,用这个原型就可以复制出更多不同版本的怪物。
为了实现这个功能,我们设计了一个基类Monster,它有一个抽象方法Clone():1
2
3
4
5
6
7class Monster
{
public:
virtual ~Monster() {}
virtual Monster* clone() = 0;
//Other stuff...
};
每一个子类monster都提供了一份特定的实现,该实现会返回一个与自身类型和状态相同的对象。例如:1
2
3
4
5
6
7
8
9
10
11class 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_;
};
生成器函数
但是我们还是需要为每一种怪物编写一个类。但是这肯定不是现在大部分游戏引擎的做法,这里仍然存在其他解决方案。我们定义孵化函数,而不再是为每一个怪物类定义生成器类,就像下面这样:1
2
3
4Monster* spawnGhost()
{
return new Ghost();
}
定义孵化函数比定义生成器要显得更简洁。这样的话每一个怪物类只要包含孵化函数指针即可:1
2
3
4
5
6
7
8
9typeof Monster* (*SpawnCallback)();
class Spawner
{
public:
Spawner(SpawnCallback spawn) : spawn_(spawn){}
Monster* spawnMonster() { return spawn_();
private:
SpawnCallback spawn_;
};
在创造幽灵生成器的时候,可以这样写:
Spawner* ghostSpawner = new Spawner(spawnGhost);
模板
我们的生成器类需要构建一些对象实例,但是我们并不想硬编码每一个怪物类。如果采用模板,则可以很自然地引入类型参数来解决这个问题。1
2
3
4
5
6
7
8
9
10
11
12class Spawner
{
public:
virtual ~Spawner() {}
virtual Monster* spawnMonster() = 0;
};
template <class T>
class SpawnerFor : public Spawner
{
public:
virtual Monster* spawnMonster() { return new T(); }
};
使用方法如下:1
Spawner* ghostSpawner = new SpawnerFor<Ghost>();
头等公民类型(First-class types)
在C++里面,Class并不是头等公民。如果你使用像Javascript、python和Ruby这样的把Class当作是头等公民的动态语言,Class可以当作函数参数进行传递,那么你会得到更优雅的解决方案。