在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
在《强大的DELPHI RTTI--兼谈需要了解多种开发语言》一文中,我说了一下我用DELPHI的RTTI实现了数据集的简单对象化。本文将详细介绍一下我的实现方法。 select * from Employee 现在要把它的内容中EmployeeID, FirstName, LastName,BirthDate四个字段显示到ListView里。传统的代码如下: With ADODataSet1 Do 1、首先是有很多代码非常冗长。比如FieldByName和AsXXX等,特别是AsXXX,必须时时记得每个字段是什么类型的,很容易搞错。而且有些不兼容的类型如果不能自动转换的话,要到运行时才能发现错误。 2、需要自己在循环里处理当前记录的移动。如上面的Next,否则一旦忘记就会发生死循环,虽然这种问题很容易发现并处理,但程序员不应该被这样的小细节所纠缠。 3、最主要的是字段名通过String参数传递,如果写错的话,要到运行时才会发现,增加了潜在的BUG可能性,特别是如果测试没有完全覆盖所有的FieldByName,很可能使这样的问题拖到客户那边才会出现。而这种写错字段名的情况是很容易发生的,特别是当程序使用了多个表时,还容易将不同表的字段名搞混。 在这个由OO统治的时代里,碰到与数据集有关的操作时,我们还是不得不常常陷入上面说的这些关系数据库方面的细节问题中。当然现在也有摆脱它们的办法,那就是O/R mapping,但是O/R mapping毕竟与传统的开发方式差别太大,特别是对于一些小的应用来说,没必要这么夸张,在这种情况下,我们需要的只是一个简单的数据集对象化方案。 在JAVA及其它动态语言的启发下,我想到了用DELPHI强大的RTTI来实现这个简单的数据集对象化方案。下面是实现与传统代码同样功能的数据集对象化应用代码: Type procedure TForm1.ListClick(Sender: TObject); 表面上看多了一个定义数据集的代理类,好像多了一些代码,但这是一件一劳永逸的事,特别是当程序中需要多次重用同样结构的数据集的情况下,将会使代码量大大减少。更何况这个代理类的定义非常简单,只是根据字段名和字段类型定义一系列的属性罢了,不用任何实现代码。其中用到的属性存取函数 GetXXX/SetXXX都在基类TMDataSetProxy里实现了。 现在再来看那段与原代码对应的循环: 1、FieldByName和AsXXX都不需要了,变成了对代理类的属性操作,而且每个字段对应的属性的类型在前面已经定义好了,不用再每次用到时来考虑一下它是什么类型的。如果用错了类型,在编译时就会报错。 2、用一个ForEach来进行记录遍历,不用再担心忘记Next造成的死循环了。 3、最大的好处是字段名变成了属性,这样就可以享受到编译时字段名校验的好处了,除非是定义代理类时就把字段名写错,否则都能在编译时发现。 现在开始讨论TMDataSetProxy。其实现的代码如下: (******************************************************************
1 unit MDSPComm;
2 3 interface 4 5 Uses 6 Classes, DB, TypInfo; 7 8 Type 9 10 TMPropList = class(TObject) 11 private 12 FPropCount : Integer; 13 FPropList : PPropList; 14 15 protected 16 Function GetPropName( aIndex : Integer ) : ShortString; 17 function GetProp(aIndex: Integer): PPropInfo; 18 19 public 20 constructor Create( aObj : TPersistent ); 21 destructor Destroy; override; 22 23 property PropCount : Integer Read FPropCount; 24 property PropNames[aIndex : Integer] : ShortString Read GetPropName; 25 property Props[aIndex : Integer] : PPropInfo Read GetProp; 26 End; 27 28 TMDataSetProxy = class(TPersistent) 29 private 30 FDataSet : TDataSet; 31 FPropList : TMPropList; 32 FLooping : Boolean; 33 34 protected 35 Procedure BeginEdit; 36 Procedure EndEdit; 37 38 Function GetInteger( aIndex : Integer ) : Integer; Virtual; 39 Function GetFloat( aIndex : Integer ) : Double; Virtual; 40 Function GetString( aIndex : Integer ) : String; Virtual; 41 Function GetVariant( aIndex : Integer ) : Variant; Virtual; 42 Procedure SetInteger( aIndex : Integer; aValue : Integer ); Virtual; 43 Procedure SetFloat( aIndex : Integer; aValue : Double ); Virtual; 44 Procedure SetString( aIndex : Integer; aValue : String ); Virtual; 45 Procedure SetVariant( aIndex : Integer; aValue : Variant ); Virtual; 46 47 public 48 constructor Create( aDataSet : TDataSet ); 49 destructor Destroy; override; 50 Procedure AfterConstruction; Override; 51 52 function ForEach : Boolean; 53 54 Property DataSet : TDataSet Read FDataSet; 55 end; 56 57 implementation 58 59 { TMPropList } 60 61 constructor TMPropList.Create(aObj: TPersistent); 62 begin 63 FPropCount := GetTypeData(aObj.ClassInfo)^.PropCount; 64 FPropList := Nil; 65 if FPropCount > 0 then 66 begin 67 GetMem(FPropList, FPropCount * SizeOf(Pointer)); 68 GetPropInfos(aObj.ClassInfo, FPropList); 69 end; 70 end; 71 72 destructor TMPropList.Destroy; 73 begin 74 If Assigned( FPropList ) Then 75 FreeMem( FPropList ); 76 inherited; 77 end; 78 79 function TMPropList.GetProp(aIndex: Integer): PPropInfo; 80 begin 81 Result := Nil; 82 If ( Assigned( FPropList ) ) Then 83 Result := FPropList[aIndex]; 84 end; 85 86 function TMPropList.GetPropName(aIndex: Integer): ShortString; 87 begin 88 Result := GetProp( aIndex )^.Name; 89 end; 90 91 { TMRefDataSet } 92 93 constructor TMDataSetProxy.Create(aDataSet: TDataSet); 94 begin 95 Inherited Create; 96 FDataSet := aDataSet; 97 FDataSet.Open; 98 FLooping := false; 99 end; 100 101 destructor TMDataSetProxy.Destroy; 102 begin 103 FPropList.Free; 104 If Assigned( FDataSet ) Then 105 FDataSet.Close; 106 inherited; 107 end; 108 109 procedure TMDataSetProxy.AfterConstruction; 110 begin 111 inherited; 112 FPropList := TMPropList.Create( Self ); 113 end; 114 115 procedure TMDataSetProxy.BeginEdit; 116 begin 117 If ( FDataSet.State <> dsEdit ) AND ( FDataSet.State <> dsInsert ) Then 118 FDataSet.Edit; 119 end; 120 121 procedure TMDataSetProxy.EndEdit; 122 begin 123 If ( FDataSet.State = dsEdit ) OR ( FDataSet.State = dsInsert ) Then 124 FDataSet.Post; 125 end; 126 127 function TMDataSetProxy.GetInteger(aIndex: Integer): Integer; 128 begin 129 Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger; 130 end; 131 132 function TMDataSetProxy.GetFloat(aIndex: Integer): Double; 133 begin 134 Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat; 135 end; 136 137 function TMDataSetProxy.GetString(aIndex: Integer): String; 138 begin 139 Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString; 140 end; 141 142 function TMDataSetProxy.GetVariant(aIndex: Integer): Variant; 143 begin 144 Result := FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value; 145 end; 146 147 procedure TMDataSetProxy.SetInteger(aIndex, aValue: Integer); 148 begin 149 BeginEdit; 150 FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsInteger := aValue; 151 end; 152 153 procedure TMDataSetProxy.SetFloat(aIndex: Integer; aValue: Double); 154 begin 155 BeginEdit; 156 FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsFloat := aValue; 157 end; 158 159 procedure TMDataSetProxy.SetString(aIndex: Integer; aValue: String); 160 begin 161 BeginEdit; 162 FDataSet.FieldByName( FPropList.PropNames[aIndex] ).AsString := aValue; 163 end; 164 165 procedure TMDataSetProxy.SetVariant(aIndex: Integer; aValue: Variant); 166 begin 167 BeginEdit; 168 FDataSet.FieldByName( FPropList.PropNames[aIndex] ).Value := aValue; 169 end; 170 171 function TMDataSetProxy.ForEach: Boolean; 172 begin 173 Result := Not FDataSet.Eof; 174 If FLooping Then 175 Begin 176 EndEdit; 177 FDataSet.Next; 178 Result := Not FDataSet.Eof; 179 If Not Result Then 180 Begin 181 FDataSet.First; 182 FLooping := false; 183 End; 184 End 185 Else If Result Then 186 FLooping := true; 187 end; 188 189 end. 190 191
其中TMPropList类是一个对RTTI的属性操作部分功能的封装。其功能就是利用DELPHI在TypInfo单元中定义的一些 RTTI函数,实现为一个TPersistent的派生类维护其Published的属性列表信息。代理类就通过这个属性列表来取得属性名,并最终通过这个属性名与数据集中的相应字段进行操作。 TMDataSetProxy就是数据集代理类的基类。其最主要的部分就是在AfterConstruction里创建属性列表。 属性的操作在这里只实现了Integer, Double/Float, String, Variant这四种数据类型。如果需要,可以自己在此基础上派生自己的代理基类实现其它数据类型的实现,而且这几个已经实现的类型的属性操作实现都被定义为虚函数,也可以在派生基类里用自己的实现取代它。不过对于不是很常用的类型,建议可以定义实际的代理类时再实现。比如前面的例子中,假设 TDateTime不是一个常用的类型,可以这样做: TDSPEmployee = class(TMDataSetProxy) { TDSPEmployee } function TDSPEmployee.GetDateTime(const Index: Integer): TDateTime; procedure TDSPEmployee.SetDateTime(const Index: Integer; 另外,利用这一点,还可以为一些自定义的特别的数据类型提供统一的操作。 另外,在所有的SetXXX之前都调用了一下BeginEdit,以避免忘记使用DataSet.Edit导致的运行时错误。 ForEach被实现成可以重复使用的,在每次ForEach完成一次遍历后,将当前记录移动最第一条记录上以备下次的循环。另外,在Next之前调用了EndEdit,自动提交所作的修改。 这个数据集对象化方案是一种很简单的方案,现在存在的最大的一个问题就是属性的Index参数必须严格按照属性在定义时的顺序,否则就会取错字段。这是因为DELPHI毕竟还是一种原生开发语言,调用GetXXX/SetXXX时区别同类型的不同属性的唯一途径就是通过Index,而这个 Index参数是在编译时就确定地传给函数了,并没有一个动态的表来记录,所以只能采用现在这样的方法来将就。 本篇文章来源于:开发学院 http://edu.codepub.com/ 原文链接:http://edu.codepub.com/2009/0803/12096_2.php |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论