一、開發背景
面向對象方法在軟件開發的分析、設計以及編碼中作用越來越重要,它 在適應系統需求變化、提高軟件可重用性和開發效率方面有著其它開發方法無法比擬的優點 。面向對象思想將應用域中的概念描述成對象,應用系統由一系列對象構成,對象之間可以 傳遞消息,系統的運作可說就是對象間的協同工作。有過開發經驗人都知道,應用系統中有 些是對象是要持久存在的,需要將它們存入磁盤,以便在重啟系統時能夠調入系統。這些對 象在面向對象方法中主要指實體對象,為了一致,本文中以實體對象代表所有需要存儲的對 象。
目前,對象存儲方式有兩種:一種是存入文件,另一種是存入數據庫。將對象存 入文件中,容易實現,操作簡便,有很多類庫已實現了此功能,但是文件存儲方式難以表示 對象之間的關系,性能上也有所不足,難以滿足大型系統的要求。將對象存入數據庫,理想 的選擇是面向對象數據庫,但面向對象數據庫雖有所發展,仍不成熟,還不能滿足需要。關 系型數據庫系統經過多年的發展,技術已經相當成熟,應用十分廣泛,大部分信息系統都以 其作為後台數據管理。如今成熟的數據庫產品有很多,為了降低在數據庫編程方面的難度, 各種數據庫訪問模型相繼問世,如ADO、ODBC、BDE、ADO.Net和JDBC等,雖然如此,關系型數 據庫並非為適應面向對象技術而存在和發展,面向對象語言和關系數據庫之間不能實現直接 對象存取,需要經過轉換,為了提高開發效率,降低應用系統與數據庫之間的耦合度,在二 者之間開發對象存儲模塊十分必要。
二、實現策略
數據庫特點。數據庫由 數據表構成,在面向對象編程中,數據表一般代表實體對象,表中的字段代表實體對象的屬 性。由於數據表一種特殊的數據結構,每個表的字段數、字段類型與長度和其他約束均不相 同,因此建立能統一表示各數據表結構的類是很難的。數據表雖在結構上有所不同,但各數 據表也有相同之處:它們都有一個名字,表上都有共同的操作,就是如查詢、增加、刪除、 修改等存取操作。
實體對象與數據表之間的映射。針對數據庫結構的特點,我們為 所有實體對象建立一個抽象基類,類中有實體對象名,有數據庫存取操作的接口,每個接口 以數據庫連接對象為參數。實體對象可從抽象基類繼承,增加相關屬性和屬性的賦值與讀取 方法,實現從抽象基類中繼承而來的數據庫存取接口。數據庫存取接口的實現策略是:讀取 實體對象名作為數據表名,用數據訪問模型建立記錄集,用記錄集中的字段值為實體對象的 屬性賦值或將屬性值存入數據表中。這樣就為實體對象與數據表建立了對應關系。
命令模式。實體對象與數據表的建立對應關系後,不能孤立存在,不能由其自身創建和存儲 ,必須采用某種機制將它們組織起來,向應用系統提供一致服務,與其他對象交互。借鑒設 計模式中的命令模式可以完成此項功能。命令模式結構如圖1所示。
圖1:命令 (Command)設計模式
⑴.命令模式解決方案描述。
命令抽象類說明所有具體 命令(ConcreteCommand)支持的接口,具體命令(ConcreteCommand)封裝了接收者 (Receiver)使用的服務。客戶創建具體命令(ConcreteCommand),並將這些具體命令 (ConcreteCommand)綁定在指定的接收者(Receiver)上,調用者(Invoker)實際執行一 條命令或取消一條命令的執行。
⑵.命令模式結論:
①命令對象和命令算法被 分離;
②調用者(Invoker)從指定的命令中分離出來並得到保護;
③具體 命令(ConcreteCommand)是對象,它們可以進行創建和存儲;
④在無須修改代碼的 前提下,新的具體命令(ConcreteCommand)可以被增加進來。
三、編程實現
現以圖書管理系統的存儲部分為例,選擇ACCESS數據庫,ADO數據訪問模型,C++語言 根據上述理論來實現對象存儲。
對象存儲由三個核心類來協同完成,分別為CEntitySet (實體集)、CEntity(實體對象)和CStorage(存儲管理)。CEntity類是所有實體對象的 抽象基類。CStorage類調用數據訪問模塊(ADO),管理與數據庫連接,並向應用系統提供對 象存儲服務。CEntitySet類是參數模板類,它根據查詢條件和對象類型,調用ADO訪問數據庫 並生成需要的實體對象集合,並向應用系統提供對象集合訪問服務。
圖書管理系統 中的對象存儲結構如圖2所示。
圖2:對象存 儲結構圖
CEntitySet類部分實現代碼。
CEntitySet類是模板類,其類型參數 必須是CEntity類的派生類。類聲明如下:
template <class T>
class CEntitySet
{
public:
CEntitySet(const CString& strName);
virtual ~CEntitySet(void);
private:
CString m_strEntityName;
CAdoConnection* m_pConn;
vector<T*> m_vecEnt;
CString m_strSQL;
public:
……
BOOL Execute(void); //根據m_strSQL的查 詢條件,生成符合條件的實體集
T* operator[](int index) const;
int GetSize(void) const;
……
};
CEntitySet類聲明中 Execute()成員函數的實現代碼如下:
template <class T>
BOOL CEntitySet<T>::Execute(void)
{
……
CAdoRecordSet rs(m_pConn);
if(rs.Open(m_strSQL) == -1)
return FALSE;
while(!rs.IsEOF())
{
CEntity* pEntity = new T(m_strEntityName);
ASSERT(pEntity );
if (pEntity == NULL)
{
rs.Close();
return FALSE;
}
if(!pEntity ->Read(&rs))
break;
m_vecEnt.push_back(static_cast<T*> (pEntity));
rs.MoveNext();
}
rs.Close();
return TRUE;
}
四、對象存儲模塊的使用
CStorage類在應用程序啟動時建 立並初始化,在應用程序退出前清理。
m_Storage.SetConnString ("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Book.mdb");
if(! m_Storage.Initialize() MessageBox("存儲系統初始化失敗!");
//借書功 能測試
CBorrowBook borrowbook; //借書類與圖書類之間的關聯視圖
CBorrow borrow; //借書類
borrowbook.SetBookNumber ("10001");
if(!m_Storage.ReadEntity(&borrowbook)) break;
borrow. SetPersonName(borrowbook.GetPersonName());
borrow. SetBorrowDate(borrowbook.GetPersonName());
if(m_Storage.SaveEntity (&borrow)) MessageBox("借書成功");
……//查找書價 >90元的圖書
CEntitySet<CBook> books("book");
books.SetConn(m_Storage.GetConn());
CBook* pBook = NULL;
books.SetSQLString("price>90");
books.Execute();
int count = books.GetSize();
m_ListCtrl.DeleteAllItems(); //列表 視圖控制
for(int index = 0; index < count; index++)
{
pBook = books[index];
m_ListCtrl.InsertItem(index, pBook- >GetBookNumber());
……
}
五、總結
從上 面的設計和源程序中可以看出,CStorage類和CEntitySet類並沒有訪問和操縱數據庫記錄集 ,數據庫的存取操作實際上由實體對象來完成,這樣對於不同的實體對象和數據表結構,系 統都可以正常工作,且即使增加新的實體對象也不會影響模塊本身結構的穩定性。
實 體對象是對應用域中概念的描述,它只應該向外提供屬性(或字段)賦值與讀取操作,其數 據庫操作接口應聲明為私有成員,不應該向應用系統提供。為了保證CStorage類和 CEntitySet類可以向數據庫存取實體對象,本模塊根據C++多態特性,在這兩個類中用指向 CEntity類的指針來調用實體對象的存取接口,這樣在實體對象將數據庫存取接口聲明為私有 成員後,整個模塊仍能正常工作。
六、附注
實體對象名必須與數據表名相 同。
本模塊的並不僅限於用C++語言實現,使用Java和C#同樣可以完成。
本 模塊作適當調整,可以適用其他的數據訪問模型,如ODBC、DAO。
使用對象存儲,數 據庫操作性能會略有下降,這也和實體類的具體實現有關。
開發環境為VC6。