注册 | 登录
收藏 | 帮助
热门文章
编辑推荐
相关文章  
从Melissa到Zotob:Windows蠕虫1
从后台得到webshell技巧大汇总
Qmail邮件系统下防止滥用mail re
Qmail邮件系统下防止滥用mail re
使用telnet 方式连接Imail的SMTP
MDeamon Server 邮件系统迁移完全
用RelayFax搭建传真服务器
Qmail邮件系统下防止滥用mail re
隐藏qmail的SMTP help信息保护你
Sendmail环境下利用DRAC杜绝垃圾
您现在的位置: 顶尖设计 >> IT学院 >> 编程开发 >> Delphi >> 文章正文
用DELPHI的RTTI实现数据集的简单对象化
作者:Raptor  来源:csdn  点击:  更新:2006-12-19
简介:
 在《强大的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、最大的好处是字段名变成了属性,这样就可以享受到编译时字段名校验的好处了,除非是定义代理类时就把字段名写错,否则都能在编译时发现。

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

(******************************************************************

用RTTI实现的数据集代理,可以简单地将数据集对象化。

Copyright (c) 2005 by Mental Studio.

Author : 猛禽

Date   : Jan.28-05

******************************************************************)

unit MDSPComm;



interface



Uses

    Classes, DB, TypInfo;



Type



    TMPropList = class(TObject)

    private

        FPropCount : Integer;

        FPropList  : PPropList;



    protected

        Function 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)

    private

        FDataSet  : TDataSet;

        FPropList : TMPropList;

        FLooping  : Boolean;



    protected

        Procedure 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);

begin

    FPropCount := GetTypeData(aObj.ClassInfo)^.PropCount;

    FPropList  := Nil;

    if FPropCount > 0 then

    begin

        GetMem(FPropList, FPropCount * SizeOf(Pointer));

        GetPropInfos(aObj.ClassInfo, FPropList);

    end;

end;



destructor TMPropList.Destroy;

begin

    If Assigned( FPropList ) Then

        FreeMem( FPropList );

    inherited;

end;



function TMPropList.GetProp(aIndex: Integer): PPropInfo;

begin

    Result := Nil;

    If ( Assigned( FPropList ) ) Then

        Result := FPropList[aIndex];

end;



function TMPropList.GetPropName(aIndex: Integer): ShortString;

begin

    Result := GetProp( aIndex )^.Name;

end;



{ TMRefDataSet }



constructor TMDataSetProxy.Create(aDataSet: TDataSet);

begin

    Inherited Create;

    FDataSet := aDataSet;

    FDataSet.Open;

    FLooping := false;

end;



destructor TMDataSetProxy.Destroy;

begin

    FPropList.Free;

    If Assigned( FDataSet ) Then

        FDataSet.Close;

    inherited;

end;



procedure TMDataSetProxy.AfterConstruction;

begin

    inherited;

    FPropList := TMPropList.Create( Self );

end;



procedure TMDataSetProxy.BeginEdit;

begin

    If ( FDataSet.State <> dsEdit ) AND ( FDataSet.State <> dsInsert ) Then

        FDataSet.Edit;

end;



procedure TMDataSetProxy.EndEdit;

begin

    If ( FDataSet.State = dsEdit ) OR ( FDataSet.State = dsInsert ) Then

        FDataSet.Post;

end;



function TMDataSetProxy.GetInteger(aIndex: Integer): Integer;

begin

    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger;

end;



function TMDataSetProxy.GetFloat(aIndex: Integer): Double;

begin

    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat;

end;



function TMDataSetProxy.GetString(aIndex: Integer): String;

begin

    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString;

end;



function TMDataSetProxy.GetVariant(aIndex: Integer): Variant;

begin

    Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value;

end;



procedure TMDataSetProxy.SetInteger(aIndex, aValue: Integer);

begin

    BeginEdit;

    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger := aValue;

end;



procedure TMDataSetProxy.SetFloat(aIndex: Integer; aValue: Double);

begin

    BeginEdit;

    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat := aValue;

end;



procedure TMDataSetProxy.SetString(aIndex: Integer; aValue: String);

begin

    BeginEdit;

    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString := aValue;

end;



procedure TMDataSetProxy.SetVariant(aIndex: Integer; aValue: Variant);

begin

    BeginEdit;

    FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value := aValue;

end;



function TMDataSetProxy.ForEach: Boolean;

begin

    Result := Not FDataSet.Eof;

    If FLooping Then

    Begin

        EndEdit;

        FDataSet.Next;

        Result := Not FDataSet.Eof;

        If Not Result Then

        Begin

            FDataSet.First;

            FLooping := false;

        End;

    End

    Else If Result Then

        FLooping := true;

end;



end.

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

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

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

    TDSPEmployee = class(TMDataSetProxy)

    protected

        function  GetDateTime(const Index: Integer): TDateTime;

        procedure SetDateTime(const Index: Integer; const Value: TDateTime);

    published

        Property EmployeeID : Integer Index 0 Read GetInteger Write SetInteger;

        Property FirstName  : String  In

[1] [2] 下一页






  • 上一篇文章:
  • 下一篇文章:
  • 分享此文:该页面添加到 Mister Wong 添加到雅虎Yahoo!收藏 Add to:Del.icio.us Post to Furl Digg this 添加到Google书签 reddit spurl blogmarks 365Key 评论  收藏  分享  打印
     我来说两句
    姓名:       验证码:   
    主页: 
    评分: 1分 2分 3分 4分 5分
    本频道近期热评文章:
      关于我们 | 联系我们 | 站点地图 | 广告投放 | 友情链接 | 在线留言 | 版权申明
    版权所有 © 2004-2007 顶尖设计(bobd.cn)
    未经授权禁止转载,摘编,复制本站内容或建立镜像. 沪ICP备07504942号 
    网络110
    报警服务