用命令模式实现对象存储——对象与关系数据库
本文是笔者根据数据库编程经验,利用C++语言的模板、继承、授权、多态等面向对象特性,借鉴命令模式,实现了对象在关系数据中的存储,降低应用系统与数据库之间的耦合,提高开发效率。
关键字:面向对象 对象存储 C++
一、开发背景
面向对象方法在软件开发的分析、设计以及编码中作用越来越重要,它在适应系统需求变化、提高软件可重用性和开发效率方面有着其它开发方法无法比拟的优点。面向对象思想将应用域中的概念描述成对象,应用系统由一系列对象构成,对象之间可以传递消息,系统的运作可说就是对象间的协同工作。有过开发经验人都知道,应用系统中有些是对象是要持久存在的,需要将它们存入磁盘,以便在重启系统时能够调入系统。这些对象在面向对象方法中主要指实体对象,为了一致,本文中以实体对象代表所有需要存储的对象。
目前,对象存储方式有两种:一种是存入文件,另一种是存入数据库。将对象存入文件中,容易实现,操作简便,有很多类库已实现了此功能,但是文件存储方式难以表示对象之间的关系,性能上也有所不足,难以满足大型系统的要求。将对象存入数据库,理想的选择是面向对象数据库,但面向对象数据库虽有所发展,仍不成熟,还不能满足需要。关系型数据库系统经过多年的发展,技术已经相当成熟,应用十分广泛,大部分信息系统都以其作为后台数据管理。如今成熟的数据库产品有很多,为了降低在数据库编程方面的难度,各种数据库访问模型相继问世,如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:CStringm_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类的指针来调用实体对象的存取接口,这样在实体对象将数据库存取接口声明为私有成员后,整个模块仍能正常工作。