用DELPHI的RTTI实现数据集的简单对象化

2016-02-19 18:50 29 1 收藏

有了下面这个用DELPHI的RTTI实现数据集的简单对象化教程,不懂用DELPHI的RTTI实现数据集的简单对象化的也能装懂了,赶紧get起来装逼一下吧!

【 tulaoshi.com - 编程语言 】

 在《强大的DELPHI RTTI--兼谈需要了解多种开发语言》一文中,我说了一下我用DELPHI的RTTI实现了数据集的简单对象化。本文将详细介绍一下我的实现方法。

      首先从一个简单的例子说起:假设有一个ADODataSet控件,连接罗斯文数据库,SQL为:

  select * from Employee

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

      现在要把它的内容中EmployeeID, FirstName, LastName,BirthDate四个字段显示到ListView里。传统的代码如下:

With ADODataSet1 DoBeginOpen;While Not Eof DoBeginWith ListView1.Add DoBegin  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;

      这里主要存在几个方面的问题:

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

      1、首先是有很多代码非常冗长。比如FieldByName和AsXXX等,特别是AsXXX,必须时时记得每个字段是什么类型的,很容易搞错。而且有些不兼容的类型如果不能自动转换的话,要到运行时才能发现错误。

      2、需要自己在循环里处理当前记录的移动。如上面的Next,否则一旦忘记就会发生死循环,虽然这种问题很容易发现并处理,但程序员不应该被这样的小细节所纠缠。

      3、最主要的是字段名通过String参数传递,如果写错的话,要到运行时才会发现,增加了潜在的BUG可能性,特别是如果测试没有完全覆盖所有的FieldByName,很可能使这样的问题拖到客户那边才会出现。而这种写错字段名的情况是很容易发生的,特别是当程序使用了多个表时,还容易将不同表的字段名搞混。

      在这个由OO统治的时代里,碰到与数据集有关的操作时,我们还是不得不常常陷入上面说的这些关系数据库方面的细节问题中。当然现在也有摆脱它们的办法,那就是O/R mapping,但是O/R mapping毕竟与传统的开发方式差别太大,特别是对于一些小的应用来说,没必要这么夸张,在这种情况下,我们需要的只是一个简单的数据集对象化方案。
  

      在JAVA及其它动态语言的启发下,我想到了用DELPHI强大的RTTI来实现这个简单的数据集对象化方案。下面是实现与传统代码同样功能的数据集对象化应用代码:

TypeTDSPEmployee = class(TMDataSetProxy)publishedProperty 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);Varemp : TDSPEmployee;beginemp := TDSPEmployee.Create( ADODataSet1 );TryWhile ( emp.ForEach ) DoWith ListView1.Items.Add DoBeginCaption := IntToStr( emp.EmployeeID );SubItems.Add( emp.FirstName );SubItems.Add( emp.LastName );SubItems.Add( FormatDateTime( 'yyyy-mm-dd', TDateTime( emp.BirthDate ) ) );End;Finallyemp.Free;End;end;

      用法很简单。最主要的是要先定义一个代理类,其中以Published的属性来定义所有的字段,包括其类型,之后就可以以对象的方式来操作数据集了。这个代理类是从TMDataSetProxy派生来的,其中用RTTI实现了从属性操作到字段操作的映射,使用时只要简单地Uses一下相应的单元即可。关于这个类的实现单元将在下面详细说明。

      表面上看多了一个定义数据集的代理类,好像多了一些代码,但这是一件一劳永逸的事,特别是当程序中需要多次重用同样结构的数据集的情况下,将会使代码量大大减少。更何况这个代理类的定义非常简单,只是根据字段名和字段类型定义一系列的属性罢了,不用任何实现代码。其中用到的属性存取函数 GetXXX/SetXXX都在基类TMDataSetProxy里实现了。

      现在再来看那段与原代码对应的循环:

      1、FieldByName和AsXXX都不需要了,变成了对代理类的属性操作,而且每个字段对应的属性的类型在前面已经定义好了,不用再每次用到时来考虑一下它是什么类型的。如果用错了类型,在编译时就会报错。

      2、用一个ForEach来进行记录遍历,不用再担心忘记Next造成的死循环了。

      3、最大的好处是字段名变成了属性,这样就可以享受到编译时字段名校验的好处了,除非是定义代理类时就把字段名写错,否则都能在编译时发现。
  

      现在开始讨论TMDataSetProxy。其实现的代码如下:

(******************************************************************用RTTI实现的数据集代理,可以简单地将数据集对象化。Copyright (c) 2005 by Mental Studio.Author : 猛禽Date   : Jan.28-05******************************************************************)unit MDSPComm;interfaceUsesClasses, DB, TypInfo;TypeTMPropList = class(TObject)privateFPropCount : Integer;FPropList  : PPropList;protectedFunction GetPropName( aIndex : Integer ) : ShortString;function GetProp(aIndex: Integer): PPropInfo;public  constructor Create( aObj : TPersistent );  destructor  Destroy; override;  property PropCount : Integer Read FPropCount;  property PropNames[aIndex : Integer] : ShortString Read GetPropName;  property Props[aIndex : Integer] : PPropInfo Read GetProp;End;TMDataSetProxy = class(TPersistent)privateFDataSet  : TDataSet;FPropList : TMPropList;FLooping  : Boolean;protectedProcedure BeginEdit;Procedure EndEdit;Function  GetInteger( aIndex : Integer ) : Integer; Virtual;Function  GetFloat(   aIndex : Integer ) : Double;  Virtual;Function  GetString(  aIndex : Integer ) : String;  Virtual;Function  GetVariant( aIndex : Integer ) : Variant; Virtual;Procedure SetInteger( aIndex : Integer; aValue : Integer ); Virtual;Procedure SetFloat(   aIndex : Integer; aValue : Double  ); Virtual;Procedure SetString(  aIndex : Integer; aValue : String  ); Virtual;Procedure SetVariant( aIndex : Integer; aValue : Variant ); Virtual;public  constructor Create( aDataSet : TDataSet );  destructor  Destroy; override;  Procedure AfterConstruction; Override;  function  ForEach : Boolean;  Property DataSet : TDataSet Read FDataSet;end;implementation{ TMPropList }constructor TMPropList.Create(aObj: TPersistent);beginFPropCount := GetTypeData(aObj.ClassInfo)^.PropCount;FPropList  := Nil;if FPropCount  0 thenbeginGetMem(FPropList, FPropCount * SizeOf(Pointer));GetPropInfos(aObj.ClassInfo, FPropList);end;end;destructor TMPropList.Destroy;beginIf Assigned( FPropList ) ThenFreeMem( FPropList );inherited;end;function TMPropList.GetProp(aIndex: Integer): PPropInfo;beginResult := Nil;If ( Assigned( FPropList ) ) ThenResult := FPropList[aIndex];end;function TMPropList.GetPropName(aIndex: Integer): ShortString;beginResult := GetProp( aIndex )^.Name;end;{ TMRefDataSet }constructor TMDataSetProxy.Create(aDataSet: TDataSet);beginInherited Create;FDataSet := aDataSet;FDataSet.Open;FLooping := false;end;destructor TMDataSetProxy.Destroy;beginFPropList.Free;If Assigned( FDataSet ) ThenFDataSet.Close;inherited;end;procedure TMDataSetProxy.AfterConstruction;begininherited;FPropList := TMPropList.Create( Self );end;procedure TMDataSetProxy.BeginEdit;beginIf ( FDataSet.State  dsEdit ) AND ( FDataSet.State  dsInsert ) ThenFDataSet.Edit;end;procedure TMDataSetProxy.EndEdit;beginIf ( FDataSet.State = dsEdit ) OR ( FDataSet.State = dsInsert ) ThenFDataSet.Post;end;function TMDataSetProxy.GetInteger(aIndex: Integer): Integer;beginResult := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger;end;function TMDataSetProxy.GetFloat(aIndex: Integer): Double;beginResult := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat;end;function TMDataSetProxy.GetString(aIndex: Integer): String;beginResult := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString;end;function TMDataSetProxy.GetVariant(aIndex: Integer): Variant;beginResult := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value;end;procedure TMDataSetProxy.SetInteger(aIndex, aValue: Integer);beginBeginEdit;FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger := aValue;end;procedure TMDataSetProxy.SetFloat(aIndex: Integer; aValue: Double);beginBeginEdit;FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat := aValue;end;procedure TMDataSetProxy.SetString(aIndex: Integer; aValue: String);beginBeginEdit;FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString := aValue;end;procedure TMDataSetProxy.SetVariant(aIndex: Integer; aValue: Variant);beginBeginEdit;FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value := aValue;end;function TMDataSetProxy.ForEach: Boolean;beginResult := Not FDataSet.Eof;If FLooping ThenBeginEndEdit;FDataSet.Next;Result := Not FDataSet.Eof;If Not Result ThenBeginFDataSet.First;FLooping := false;End;EndElse If Result ThenFLooping := true;end;end.

      其中TMPropList类是一个对RTTI的属性操作部分功能的封装。其功能就是利用DELPHI在TypInfo单元中定义的一些 RTTI函数,实现为一个TPersistent的派生类维护其Published的属性列表信息。代理类就通过这个属性列表来取得属性名,并最终通过这个属性名与数据集中的相应字段进行操作。

      TMDataSetProxy就是数据集代理类的基类。其最主要的部分就是在AfterConstruction里创建属性列表。

      属性的操作在这里只实现了Integer, Double/Float, String, Variant这四种数据类型。如果需要,可以自己在此基础上派生自己的代理基类实现其它数据类型的实现,而且这几个已经实现的类型的属性操作实现都被定义为虚函数,也可以在派生基类里用自己的实现取代它。不过对于不是很常用的类型,建议可以定义实际的代理类时再实现。比如前面的例子中,假设 TDateTime不是一个常用的类型,可以这样做:

TDSPEmployee = class(TMDataSetProxy)protectedfunction  GetDateTime(const Index: Integer): TDateTime;procedure SetDateTime(const Index: Integer; const Value: TDateTime);publishedProperty 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  : TDateTime Index 3 Read GetDateTime Write SetDateTime;end;{ TDSPEmployee }function TDSPEmployee.GetDateTime(const Index: Integer): TDateTime;beginResult := TDateTime( GetVariant( Index ) );end;procedure TDSPEmployee.SetDateTime(const Index: Integer;  const Value: TDateTime);beginSetVariant( Index, Value );end;

      这样下面就可以直接把BirthDate当作TDateTime类型使用了。

      另外,利用这一点,还可以为一些自定义的特别的数据类型提供统一的操作。

      另外,在所有的SetXXX之前都调用了一下BeginEdit,以避免忘记使用DataSet.Edit导致的运行时错误。

      ForEach被实现成可以重复使用的,在每次ForEach完成一次遍历后,将当前记录移动最第一条记录上以备下次的循环。另外,在Next之前调用了EndEdit,自动提交所作的修改。
  

      这个数据集对象化方案是一种很简单的方案,现在存在的最大的一个问题就是属性的Index参数必须严格按照属性在定义时的顺序,否则就会取错字段。这是因为DELPHI毕竟还是一种原生开发语言,调用GetXXX/SetXXX时区别同类型的不同属性的唯一途径就是通过Index,而这个 Index参数是在编译时就确定地传给函数了,并没有一个动态的表来记录,所以只能采用现在这样的方法来将就。

来源:http://www.tulaoshi.com/n/20160219/1619150.html

延伸阅读
在Windows95/98中,都是使用注册表对系统数据进行管理,有关壁纸的设置数据保存在Hkey_Current_UserControl PanelDesktop的Wallpaper和TileWallpaper 等键值中,只要成功修改了这两个键值,然后发消息给Windows即可更换壁纸。在本例的程序中,使用了一个Tform;两个Tspeedbutton(Speedbutton1用于接受用户的浏览命令,Speedbutton2用于接受用户的...
标签: Delphi
  给单位开发软件,涉及一打印模块,我感到颇有兴趣,就拿来其中的一个小功能模块与读者共享。下面以打印在纸张的矩形框内为例简单介绍: 程序要求: 单击[打印]按钮,把Memo的内容最多分三行打印出来,每行最多能容纳22个三号字,限定汉字上限为50个汉字。 编程思路: 用LineTo和MoveTo函数画一矩形框,根...
标签: ASP
  用ADO的COMMAND对象实现对WEB数据库动态数据查询的方法 ★ 林碧英 众所周知,由于ASP技术的出现,使得Intranet的应用更加广泛深入。相当多的企业都建立了企业内部综合查询系统,如何快速、准确地查询企业内部信息是编写基于WEB技术应用程序必须要解决的主要问题。ASP提供了用ADO内置的3个主要对象Recordset、Connection和Command对WEB...
  可视化控件(Visual Component)实际上就是一个类(class),要编写一个类,可以直接在*.pas文件中编写。但是要编写控件,则必须使用包(package)。从File菜单中选择New,新建一个Package,这就是存放和安装控件用的包。然后单击Package窗口中的Add按钮,添加一个元件(Unit)。 在弹出的对话框最上方选择New Component。因为一...
为了将数据保存到PropertyBag对象,可以先建立一个对象新的实例。使用WriteProperty方法来保存数据,这一方法包含有三个参数:识别属性的字符串,属性的数值,一个缺省数值。如果属性的数值与缺省数值相同时,属性的数值将不会被保存。其范例如下: DimobjPBAsPropertyBag DimstrValueToPersistAsString strValueToPersis...

经验教程

994

收藏

97
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部