面向对象有一个术语:Persistence,意思就是把对象永久保存下来。Power一关,啥都没有,对象如何能够永久保存呢?当然是写到文件中去了。把数据写到文件,很简单。在Document/CView结构中,数据都放在一份document里,我们只要把其中的成员变量依次写进文件即可。成员变量很肯能是一个对象,我们首先应该记载其类名称,然后才是对象中的数据。读取文档就有点麻烦了,当程序从文档中读到一个类名称时,它如何实例化(instance)一个对象?呵呵,这不就是动态创建的技术吗,我们在前面文章中已经解决掉了。 MFC有一套Serialize机制,目的在于把文件名的选择、文件的开关、缓冲区的建立、数据的读写、提取运算符>>和插入运算符<<的重载、对象的动态创建等都包装起来。上述Serialize的各部分工作,除了数据的读写和对象的动态创建外,其余都是枝节。动态创建的技术已经解决,让我们集中火力,分析数据的读写操作。
假设我有一份文件,用于记录一张图形,图形只有3种基本元素:线条(Stroke)、圆形、矩形。我打算用类来组织这份文件。
class CMyDoc : public CDocument
{
CObList m_graphList;
CSize m_sizeDoc;
//...
};
//--------------------------------
class CStroke : public CObject
{
CDWordArray m_ptArray;
//...
};
//--------------------------------
class CRectangle : publc CObject
{
CRect m_rect;
//...
};
//--------------------------------
class CCirle : public CObject
{
CPoint m_center;
UINT m_radius;
//...
};
其中COblist和CDwordArray是MFC提供的类,前者是一个链表,可放置任何从CObject派生下来的对象,后者是一个数组,每一个元素都是double word。另外3个类:CStroke、CRectangle、CCircle是我从CObject中派生下来的类。
假设有一份文件,文件中的数据为一个链表CObList m_graphList;其中链表中包含了100条线条、50个圆形、80个矩形、难道我们要记录230个类名称不成?当然不必,我们可以在每次记录对象内容的时候,先写入一个代码,表示此对象的类是否曾在文件中记录过了。如果是新类,乖乖的记录其类名称;如果是旧类,则以代码表示,这样可以节省文件大小以及程序用于解析的时间。啊,不要看到文件大小就想到硬盘很便宜,桌上的一切都将被带到网上,你得想想网络带宽这回事。
还有一个问题。文件的版本如何控制,旧版程序读取新版文件,新版程序读取旧版文件,都可能出问题,为了防弊,最好把版本号记录上去,最好是每个类都有自己的版本号码。
我希望有一个专门负责Serialization的函数,就叫做Serialize好了,假设现在我的Document类名称为CScribDoc,我希望有这么便利的程序方法:
void CScribDoc::Serialize(CArchive& ar)
{
if(ar.IsStoring())
ar<<m_sizeDoc;
else
ar>>m_sizeDoc;
m_graphList.Serialize(ar);
}
//----------------------------------------
void CObList::Serialize(CArchive& ar)
{
if(ar.IsStoring())
{
ar<<(WORD)m_nCount;
for(CNode* pNode=m_pNodeHead;pNode!=NULL;pNode=pNode->pNext)
ar<<pNode->data;
}
else
{
WORD nNewCount;
ar>>nNewCount;
while(nNewCount--)
{
CObject* newData;
ar>>newData;
AddTail(newData);
}
}
}
//-------------------------------------------------------------------------------
void CStroke::Serialize(CArchive& ar)
{
m_ptArray.Serialize(ar);
}
//----------------------------------------------
void CDWordArray::Serialize(CArchive& ar)
{
if(ar.IsStoring())
{
ar<<(WORD)m_nSize;
for(int i=0;i<m_nSize;i++)
ar<<m_pData[i];
}
else
{
WORD nOldSize;
ar>>nOldSize;
for(int i=0;i<nOldSize;i++)
ar>>m_pData[i];
}
}
//---------------------------------------------------------
void CRectangle::Serialize(CArchive& ar)
{
if(ar.IsStoring())
ar<<m_rect;
else
ar>>m_rect;
}
//---------------------------------------------------------
void CCircle::Serialize(CArchive& ar)
{
if(ar.IsStoring())
{
ar<<(WORD)m_center.x;
ar<<(WORD)m_center.y;
ar<<(WORD)m_radius;
}
else
{
ar>>(WORD)m_center.x;
ar>>(WORD)m_center.y;
ar>>(WORD)m_radius;
}
}
每一个可写到文件或可从文件读取的类,都应该有自己的Serilize函数,负责把自己的数据读写文件操作。此类并且应该改写<<运算符和>>运算符。把数据导流到archive中,archive是什么?是一个与文件息息相关的缓冲区,暂时你可以想象他就是文件的化身。当CObList列表写入文件时,以此调用每个类的Serialize函数。
DECLARE_SERIAL/IMPLEMENT_SERIAL宏
要将<<和>>两个运算符重载,还要让Serialize函数神不知鬼不觉的放入类声明之中,最好的做法仍然是宏。类之所以能够进行文件读写操作,前提是拥有动态创建的能力,所以,MFC设计了两个宏DECLARE_SERIAL和IMPLEMENT_SERIAL:
#define DECLARE_SERIAL(class_name)/
DECLARE_DYNCREATE(class_name)/
friend CArchive& AFXAPI operator>>(CArchive& ar,class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name,base_class_name,wSchema)/
CObject* PASCAL class_name::CreateObject()/
{return new class_name;}/
_IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,/
class_mame::CreateObject)/
CArchive& AFXAPI operator>>(CArchive& ar,class_name* &pOb)/
{pOb=(class_name*)ar.ReadObject(RUNTIME_CLASS(class_name));/
return ar; }/
为了在每一个对象被处理(读或写)之前,能够处理琐屑的工作,诸如是否是第一次出现、记录版本号码、记录文件名等工作,CRuntimeClass需要2个函数Load和Store,这两个函数需要在CRuntimeClass结构体中增加。
为了让整个Serialization机制运行起来,我们必须做这样的类声明:
class CScribDoc : public CDocument
{
DECLARE_DYNCREATE(CScribDoc)
};
class CStroke : public CObject
{
DECLARE_SERIAL(CStroke)
public:
void Serialize(CArchive&);
};
class CRectangle : public CObject
{
DECLARE_SERIAL(CRectangle)
public:
void Serialize(CArchive&);
};
class CCircle : public CObject
{
DECLARE_SERIAL(CCircle)
public:
void Serialize(CArchive&);
};
//在.cpp文件中进行这样的操作:
IMPLEMENT_DYNCREATE(CScribDoc,CDocument)
IMPLEMENT_Serialize(CStroke,CObject,2)
IMPLEMENT_Serialize(CRectangle,CObject,1)
IMPLEMENT_Serialize(CCircle,CObject,1)
然后分头设计CStroke、CRectangle、CCircle的Serialize函数即可。