[mental studio]猛禽[blog]
在《强大的delphi rtti--兼谈需要了解多种开发语言》一文中,我说了一下我用delphi的rtti实现了数据集的简单对象化。本文将详细介绍一下我的实现方法。
首先从一个简单的例子说起:假设有一个adodataset控件,连接罗斯文数据库,sql为:
select * from employee
现在要把它的内容中employeeid, firstname, lastname,birthdate四个字段显示到listview里。传统的代码如下:
with adodataset1 do
begin
open;
while not eof do
begin
with listview1.add do
begin
caption := inttostr( fieldbyname( 'employeeid' ).asinteger );
subitems.add( fieldbyname( 'firstname' ).asstring );
subitems.add( fieldbyname( 'lastname' ).asstring );
subitems.add( formatdatetime( fieldbyname( 'birthdate' ).asdatetime ) );
end;
next;
end;
close;
end;
这里主要存在几个方面的问题:
1、首先是有很多代码非常冗长。比如fieldbyname和asxxx等,特别是asxxx,必须时时记得每个字段是什么类型的,很容易搞错。而且有些不兼容的类型如果不能自动转换的话,要到运行时才能发现错误。
2、需要自己在循环里处理当前记录的移动。如上面的next,否则一旦忘记就会发生死循环,虽然这种问题很容易发现并处理,但程序员不应该被这样的小细节所纠缠。
3、最主要的是字段名通过string参数传递,如果写错的话,要到运行时才会发现,增加了潜在的bug可能性,特别是如果测试没有完全覆盖所有的fieldbyname,很可能使这样的问题拖到客户那边才会出现。而这种写错字段名的情况是很容易发生的,特别是当程序使用了多个表时,还容易将不同表的字段名搞混。
在这个由oo统治的时代里,碰到与数据集有关的操作时,我们还是不得不常常陷入上面说的这些关系数据库方面的细节问题中。当然现在也有摆脱它们的办法,那就是o/r mapping,但是o/r mapping毕竟与传统的开发方式差别太大,特别是对于一些小的应用来说,没必要这么夸张,在这种情况下,我们需要的只是一个简单的数据集对象化方案。
在java及其它动态语言的启发下,我想到了用delphi强大的rtti来实现这个简单的数据集对象化方案。下面是实现与传统代码同样功能的数据集对象化应用代码:
type
tdspemployee = class(tmdatasetproxy)
published
property employeeid : integer index 0 read getinteger write setinteger;
property firstname : string index 1 read getstring write setstring;
property lastname : string index 2 read getstring write setstring;
property birthdate : variant index 3 read getvariant write setvariant;
end;
procedure tform1.listclick(sender: tobject);
var
emp : tdspemployee;
begin
emp := tdspemployee.create( adodataset1 );
try
while ( emp.foreach ) do
with listview1.items.add do
begin
caption := inttostr( emp.employeeid );
subitems.add( emp.firstname );
subitems.add( emp.lastname );
subitems.add( formatdatetime( 'yyyy-mm-dd', tdatetime( emp.birthdate ) ) );
end;
finally
emp.free;
end;
end;
用法很简单。最主要的是要先定义一个代理类,其中以published的属性来定义所有的字段,包括其类型,之后就可以以对象的方式来操作数据集了。这个代理类是从tmdatasetproxy派生来的,其中用rtti实现了从属性操作到字段操作的映射,使用时只要简单地uses一下相应的单元即可。关于这个类的实现单元将在下面详细说明。
表面上看多了一个定义数据集的代理类,好像多了一些代码,但这是一件一劳永逸的事,特别是当程序中需要多次重用同样结构的数据集的情况下,将会使代码量大大减少。更何况这个代理类的定义非常简单,只是根据字段名和字段类型定义一系列的属性罢了,不用任何实现代码。其中用到的属性存取函数 getxxx/setxxx都在基类tmdatasetproxy里实现了。
现在再来看那段与原代码对应的循环:
1、fieldbyname和asxxx都不需要了,变成了对代理类的属性操作,而且每个字段对应的属性的类型在前面已经定义好了,不用再每次用到时来考虑一下它是什么类型的。如果用错了类型,在编译时就会报错。
2、用一个foreach来进行记录遍历,不用再担心忘记next造成的死循环了。
3、最大的好处是字段名变成了属性,这样就可以享受到编译时字段名校验的好处了,除非是定义代理类时就把字段名写错,否则都能在编译时发现。