使用特定原型实例来创建特定种类的对象,并且通过拷贝原型来创建新的对象。
(摘自《游戏编程模式》)
原型模式很简单,简单到我们看到描述的定义就能知道大概如何实现。然而,在当时没有泛型编程的时代,能够将代码按照原型模式进行设计也是花费了很大功夫去组织的。我们需要体会设计一步步实现的思考过程。
原型模式主要用于新实例的生成。我们通过这一模式将直接通过new生成的一类对象封装成一个函数。当我们需要新的实例时,仅需调用封装的方法即可获得复制了已存在的实例状态的新实例。
问题引入
《植物大战僵尸》这款游戏中,僵尸都是源源不断地从某一个方向生成并向农场移动的。下面是截取百度百科的三种僵尸类型:普通僵尸、旗帜僵尸、路障僵尸。
普通的怪物类和生成类
我们可以通过如下简单的代码实现这三种怪物的抽象和生成。
//僵尸基类
class Zombie
{
float attackDamage;
float speed;
float health;
private:
Mesh model;
Texture skin;
}
//生成器基类
class Spawner
{
public:
virtual ~Spawner(){}
virtual Zombie* SpawnZombie()=0;
}
//僵尸派生类
class CommonZombie : Zombie {}
class FlagZombie : Zombie {}
class ConeheeadZombie : Zombie {}
//生成器派生类
class CommonZombieSpawner : public Spawner
{
virtual Zombie* SpawnZombie(){return new CommonZombie();}
}
class FlagZombieSpawner : public Spawner
{
virtual Zombie* SpawnZombie(){return new FlagZombie();}
}
class FConeheeadZombieSpawner : public Spawner
{
virtual Zombie* SpawnZombie(){return new ConeheeadZombie();}
}
事实上,这样编写的代码没有任何问题,泛型代码在编译过后也将会展开成与此类似的样板。然而,我们仍应该更优雅的组织这些代码,原型模式提供了一种解决方案。
第一层封装:将克隆代码封装在对应类型中
我们想要减少生成器类的数量,导致生成类数量增加的原因就是每一个类实例的生成调用的构造函数不同。因此,将new实例放置在对应类中,即可减少生成类的数量——我们仅需调用父类的克隆函数即可。
//改进后Zombie基类
class Zombie
{
//...
protected:
virtual Zombie* Clone(){ return new Zombie();
}
//普通僵尸类改变示例
class CommonZombie : Zombie
{
//...
virtual Zombie* Clone(){ return new CommonZombie();}
}
//改进后的生成器
class Spawner
{
public:
Spawner(Zombie* _protoType) : protoType(_protoType){}
~Spawner(){}
Zombie* SpawnZombie(){ return protoType->Clone();}
private:
Zombie* protoType;
}
这样编写代码实际上是借助面向对象虚函数的多态特性来减少类数量。
进一步舍弃针对每一实例类型的Clone方法
即便如此,我们仍然需要针对每一个实例类型编写一个Clone()函数。是否有方法将它舍弃?当然有。我们需要想办法将clone()作为一个独立的部分而非类型的部分。将其作为全局孵化函数即可。
//全局函数
typedef Zombie* (*SpawnCallback)();
Zombie* SpawnCommonZombie() { return new CommonZombie();}
//改进后的生成器
class Spawner
{
public:
Spawner(SpawnCallback _callback) : spawner(_callback);
Zombie* SpawnZombie(){ return spawner();}
private:
SpawnCallback spawner;
}
这样编写代码实际上是借助了函数指针作为参数实现的多态特性以实现解耦。
代码层面的特性:模板
模板编程是当今重要的编程特性。模板可以很好的解决我们在上面仅仅因为类型不同而必须编写功能类似的重复代码。还是僵尸生成器为例,上述分别以虚函数、函数指针两种方式实现了一个Spawner类实现生成不同类型僵尸的功能,如果使用模板,则可以改进为:
template<class T>
class Spawner
{
public:
Spawner(){}
template<class T>
Zombie* SpawnZombie(){ return T();}
}
总结和拓展
- 原型模式实质上就是一种生成实例的模式。在生成实例的过程中,要注意深拷贝和浅拷贝的区别。
- 深拷贝:数值型字段和引用型字段是完全拷贝的。克隆体与原型是独立、无联系的。
- 浅拷贝:数值型字段拷贝,而引用型字段仅仅拷贝“引用部分”,也就是说和原型公用一个引用变量,当原型改变时,克隆体的引用变量也会跟着改变
- 原型模式可以作为一种语言的范式。
- 注意本篇文章如何运用虚函数、指针函数和模板来实现多态。