创建型模式的使用
GOF94的创建型模式,总共有五种。其中一种的范围是类的,四种是对象的。
1.Factory Method(virtual constructor)。
工厂方法在创建型模式里面的使用的频率是很高的,也是非常容易使用的。
由于工厂方法本身的一些特点,它们往往被说明成为静态的,如果这个静态函数就是待创建的类型的静态成员函数,那么,这就是Meyer所谓的
virtual constructor。(More Effective C++)这个东东很是厉害,我们可以利用它,依据数据创建正确的对象。
在游戏的过程中,存档和取档是在所难免的。在存储的过程中,你需要将一个自定义类串行化到文件中。这个到不是什么难事。但是如果你要从文件中读取对象呢?
首先你就需要知道这个对象的类,然后才能创建啊。这个时候,virtual constructor便可以显示出它的作用来。什么?你知道了?好,你写一
段代码给我看看。
TypeID id;//类标识,每个类唯一,可以籍此判断类型
switch (id)
{
case NPC_ID :
//创建NPC,读入数据。。。
case MONSTOR:
}
不不不,我绝对不是叫你要这么写!这段代码的代价太大了。有效率的做法是,我们使用一个映射表。方法不是静态的吗?那好,我就给它统一一个函数声明:
typedef (void*)(*PFnCreateObject)(ByteStream& stream/*资源流*/);
注意,这后面的这个void*理论上是需要返回一个指针,而这个指针又因为类型转换而失去了识别类型的作用。但是不要忘了,我们调用的是一个函数,一切需要弥补的缺陷,都可以在函数中完成。
因此,我们可以这样写一个查找表:
PFnCreateObject creatorFuncTable[MAX_TYPE_ID];
在程序需要读取存档的时候,我们可以:
void Load(ByteStream& stream)
{
//
TypeID id;
stream.Read(&id);
(*creatorFuncTable[id])(stream);
//
}
这种做法的麻烦之处在于要为每一个类开一个ID。如果是手工完成这一项的话,是很需要点功夫的。而且维护起来也不是很方便。因此,这里可以用GUID再HASH的办法获取一个Hash表。保存对象时要保存相应的GUID,查找时使用Hash查找。
顺便说一下,如果有个非虚函数 Foo,有个类
class A
{
void foo() {
if(this == NULL) {
//操作,但是不能调用A类的非静态成员或虚函数
cout << "Aha! 还是能运行哦!" << endl;
} else {
cout << "没什么了不起的,地球人都知道!" << endl;
}
}
};
//
A* p = NULL;
p->foo();
这个调用是正确的!因为除了使用ECX传入NULL(this)以外,并没有非法的内存操作。因此,运行时也不会有错误。 当然,以上的调用实际上是不可取的。而且当foo为虚函数的时候,这种调用就不能正确进行了,因为虚函数是先要访问虚函数表的,而虚函数表又是对象而不是类的一部分,调用了就访问了错误的地址,所以其行为是不确定的。
所以说,Factory Method 的特点就是:依据不同条件,创造不同型别。(这里的条件就是烦人的类型ID)在创建之前,我们不能确定物体的型别。
如果我们将静态函数变为一个类(不是被创建的类)的成员函数,那么,这个结构就和Gof94上的描述一样了。Gof94上的
Factory Method,有着它自己的特点,这一点请参见书本。这里与Gof94的带继承的工厂方法相比,只是说对于不同的创建条件,构造函数的分
派方式不同而已。(这点我将在下面讲述到)
通过对工厂方法的使用,我们可以实现很多的功能,例如利用池分配等等。其中的一些功能,我们也可以通过重载operator new和
operator delete的办法实现,但是一些其它的功能,这种方法实现起来就会很吃力或者不可行,那么,工厂方法就为我们在创建的时候便搭建了一
个足够我们恣意施展才华的场所。