随手涂鸦的:
?
// HellowTest2008.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "CLock.h" /* V1表示旧的数据,V2表示扩充的数据 旧数据nVersion表示为0, 扩充数据nVersion表示为1 */ struct Role1 { int V1n1; int V2n1; }; struct Role2 { int V2n1; int V1n1; }; class CRole { public: private: public: int V1n1; Role1 V1Role1; Role2 V1Role2; int V2n1; int V2n2; int V2n3; private: }; struct ChunkHead { int nLen; }; struct Chunk { int nLen; int ChunkType; char Buffer; int CRC; }; /* CSave表示一个已经从介质里读取出来的数据 */ class CSave { public: CSave(); void V1Read(CRole *pRole); void V1Write(CRole *pRole); void V2Read(CRole *pRole); private: public: /* V1 */ int nVersion; char Buffer[1024]; /* V2 */ char V2Buffer[1024]; //这段的组成规则是ChunkHead|Chunk|Chunk|Chunk private: }; CSave::CSave() { nVersion = 0; } void CSave::V1Read(CRole *pRole) { /* BufferXXX表示产出一个最终结果值,这里我们不关心 */ if(0==nVersion) { pRole->V1n1 = BufferXXX; /* 每个结构是否有自己的序列化函数,这都不重要.关心的是一个写值的动作 */ pRole->V1Role1.V1n1 = BufferXXX; pRole->V1Role1.V1n1 = BufferXXX; } /* 扩充后,根据版本号来读取。 这里的扩充一般会有两种情况, 1.先扩充存储介质(比如数据库) 2.先扩充代码,根据写入时来自动扩充数据库 3.两边一起扩充 如果是第一种,或许不要版本号,只要代码扩充好,然后把介质中新加的字段一个初始值, 最后再精心修改下0==nVersion中的代码,算好介质中各种新加入的偏移值。这明显是最差的,把同介质之间的接口作用放大。 如果是第二种(大多数下是),首先在读的时候仍然按照旧版本读(这个时候介质还没扩充),写入时按照 新版本的写,然后修改版本号。 如果是第三种,其实跟第一种类似,把同介质之间的接口作用放大。还要需要修改数据库代码。而数据库,更倾向看成是一个 read,write的东西。只要关心数据和长度这两个参数即可.如果你想要查看,我觉得单独做个读取工具更好。而且这段读取的 代码和你真正工程里读取数据的代码区别不大。 下面代码给出第二种方法 */ if(1==nVersion) { //先调用0==nVersion的函数读一遍 pRole->V1Role1.V2n1 = BufferXXX; pRole->V2n1 = BufferXXX; //...... } if( 2==nVersion ) { //掉调用1==nVersion的。。 //...... } /* 这种用版本号的方法可以解决兼容的问题。但是不好的地方是让第二个版本的数据知道了第一个版本的数据,在 数据的意义上它们是等同的,但是处理方法却变成了一种兼容的方式。同时,BufferXXX获取的接口中,也要从一个 大数据里面取出对应的第二个版本数据。这些都明显增加了二次开发和扩充的代价。我以前一次工作中,数据库的设计 是针对每个角色都有特别大的空间,防止以后扩充不够。结果每次增加新东西时,总要奔赴在修改各种旧的代码之间, 即使你不用动它,它们也总是在你调试的时候跳来跳去。更可恶的是数据库的字段名,一开始只是简单的叫d1,d2,d3...dn, 在核对各个字段上又浪费了不少时间。结果在扩充一些大功能的时候,另外建立了单独的空间,终于让它们享受一样的 待遇了。 于是就想,为什么不把扩充的数据也当成一个全新的数据,只是让他们最终流向的Role结构不一样而已。完全可以 让它们在新旧版本上的数据流程都一样,就像新写了一遍。 */ } void CSave::V2Read(CRole *pRole) { /* 每个新加的数据只要按Chunk块排好,读、写总的函数完全不用变,每个人关心自己的 HandleChunkType()即可。 */ int nHasLen = 0; while(ChunkHead.nLen>nHasLen) { switch(Chunk.ChunkType) { HandleChunkType(); } nHasLen += Chunk.nLen; } } void CSave::V1Write(CRole *pRole) { /* 写这里相对简单, 因为得到的都是最后一组、最新的数据。不做更多的阐述 */ } int _tmain(int argc, _TCHAR* argv[]) { return 0; }?
但是,结合PNG的这种格式,仍然可以在游戏中做点事情。比如可以在扩充字段时,旧的方案需要修改2-3个点,完全可以改为只要修改一个点就够了。
修改的2-3个点包括,对数据库字段,SQL语句,变量的读取。如果有一些中间件变化得可能更多。
用一些小技巧,可以把这些修改点都集中到一个函数上。所以PNG还是有很大启发的。