尾牙、台湾并再谈Gullibility

帮主上周末去吃尾牙宴了,偶在那随便扯了几句关于尾牙的瞎话。后来考证了一下,是不对的。见《尾牙》和《台湾春节习俗拾趣》。显然这一风俗来自于闽南,而不是台湾的本土风俗。

说到这里,偶又想到老罗胡扯的关于台湾的那些话了。按老罗所说,偶就查了一下国内的资料《台湾历史》。

从进化论的角度上说,岛屿的生态环境太过于狭小,所以物种的进化不可避免地与大陆上有千丝万缕的联系。按地质资料来说,台湾在1.5万年前与大陆是连成一体的,这样看来,所谓的原住民,就应该是在此之前生活在台湾的原始人的后代。当然不排除之后的一万多年间陆续有少量的大陆人迁入。而老罗所谓的“狰狞的汉人”把台湾原住民赶到山上,然后管人家叫“高山族”的说法大约是指这个:

公元230年(三国吴黄龙二年),吴主孙权曾派将军卫温、诸葛直率领1万水军渡海到达台湾。

现在的台湾人,除了原住民以外,还有所谓的本省人,外省人和客家人。而在这其中,本省人主要讲闽南话(所谓的台语),占人口的约60%,讲客家话的客家人占到约20%,剩下约20%是讲其它方言的外省人和原住民。参见《台湾方言的故乡在中原》和《张克辉:警惕台湾有人利用语言文化分裂祖国》。这些人中的本省人是早年从福建迁过去的,按闽南话的起源来分析,闽南话是古汉语的一种,接近于唐代人的话。可见这些人是在唐代左右从中原迁入福建的。而客家人是在南宋时从中原迁到福建的,因为有500年左右的时间差,所以在人数上,闽南话人口比例要高于客家人。几千年来随着朝代更替,不断地有人从福建过去,使台湾的汉人数量逐渐增加。从语言上就可以看出汉人在台湾早已经占了绝对大多数–包括本省人、客家人和外省人(解放战争时随国民党军过台湾的全国各地的人)。

再从历史上看。除了上面说到的三国时吴国入侵台湾以外,后来的隋唐时期,台湾与琉球群岛主要是一种从属于中国的身份,直到元朝被并入中国的版图。明代开始对台湾的管理更加加强,出现大规模的汉人移民,特别是在明末郑芝龙(郑成功之父)等人的组织下开发台湾。1622年,荷兰殖民者入侵台湾,1626年,西班牙殖民者占领台湾北部,明朝政府内外交困无力顾及。明亡后,福建总兵郑芝龙降清,郑芝龙之子郑成功等人在福建等地扶持南明流亡政府,继续反抗满清。1661年,郑成功收复台湾,次年,荷兰殖民总督揆一签字投降。后郑成功及其子郑经以台湾为根据地,继续进行反清复明的斗争。后郑成功的部下施琅降清,并于1683年率军进攻台湾,郑成功之孙郑克爽归顺。之后再到1894年甲午中日战争中国战败后,1895年的马关条约将台(湾)澎(湖)金(门)马(祖)割给日本,直到1945年日本战败投降,日本在台湾实行了五十年的奴役统治。据统计在这五十年里,台湾人口大约被屠杀了六分之一,就像李敖说的,台湾就那么点大的地方,这些精英被杀光了,人民的精神就没有了。这也就是现在台独的基础。

其实台湾也挺惨的,虽然元朝时就并入中国,但明末清末时,政府一旦顾不上,就把台湾踢在一边不管。sigh~~~

当然所谓的“神圣不可侵犯”与“反清复明”这样的口号没什么区别,老罗也说得很实在,两岸争来争去都是汉人在争。从上面说的历史可见,现在的台湾人最大部分的人口都是明清时期迁入台湾的,所以他们的很多风俗习惯都与闽南地区相同。搞台独无非是统治阶级(都是汉人)为了自身利益考虑而已,哪里有什么可能是为了原住民。但就大陆自身利益考虑是无论如何不能让台湾独立的,要能让琉球独立那就更好乐。^O^

何况要说台湾是原住民的也未必就合适,且不说汉人在台湾生活了一千三百多年,而且其中大部分人口也至少在台湾生活了三百多年,而且要是从更远古说起,台湾也是大陆的一部分,原住民在原始时代也是中国人的一部分。

老罗在上课的时候为了活跃课堂气氛,瞎扯一些胡闹的内容无可厚非。但要是老罗说什么,都认为是真的话,那可是真是Gullibility了。也难怪他敢说,他要是一门心思搞邪教,可以搞比李红痔大十倍。

其实老罗不是个很诚实的人。

比如老罗为了证明他的RP比较好,拿了所谓的Emliy Dickinson的一首送给前男友的诗为证,但是很不幸被C彩旗考证出来说:

p.s.据坊间花边小道消息及不完全统计。偶亲爱滴荻更生同学。毕生。只有一个男友的说。

见《hospital》,我回复了句说:

C彩旗的小道消息告诉我们,老罗家里没有一棵樱桃树。-_-|||

听那段话的感觉,他们当时正在讲的题目便是与华盛顿家的樱桃树那个故事有关的,所以老罗才掰了这么些个故事的。

考证这种事虽然有点BT,但至少可以避免一些Gullibility的事情。^O^

BTW:本周第一8即告失败,因为C彩旗又补充道:

再次据坊间花边小道消息及不完全统计。偶亲爱滴荻更生同学。的确是被毕生仅有的这一个男友抛弃乐。~-_-||||

这么说老罗家里还是有樱桃树的,是偶考虑不周,灭想到有“前男友”并不表示还有“现男友”或“后男友”。-_-|||

还是麻烦

自从昨天搞定XML的持久化后,本想也试试在DELPHI里实现Dynamic Proxy–如果可以实现,那就意味着有可能可以DELPHI里实现AOP乐。结果发现难啊。

用COM的IDispatch倒是有点希望,但是这样就跟COM绑定在一起了,这是我所不希望的。

研究了一下DELPHI里的SOAP实现,用直接操作VTAB的方式可能可以,但是酱紫感觉比较不爽。

所以说,还是麻烦啊。

有时偶在想,偶是不是在干重新发明的轮子的事。-_-|||

这些东西在JAVA里都已经有了嘛。

不过话说回来,那些搞JAVA的人试图用Dynamic Proxy来实现AOP,感觉也是像是对maxin的一种模仿。

今天再想想办法,可以实现最好,不能实现就算了,已经几天没有8logging乐。^O^

BTW:目前的结果是–如果不用IDispatch的话,只能用VTAB。如朋友“太可怕”所说,MIDAS的SocketConnection就是基于IDispatch的。这个应该从Delphi 4就有了。没想到啊没想到。

BTW:看了一天RIO的源码,原来是用传说中的Thunk技术实现的,麻烦,看来要实现Dynamic Proxy虽然有可能,但还是很麻烦,留到过年时有空再研究吧。-_-

用DELPHI的RTTI实现对象的XML持久化

    去年我花了很多时间尝试用DELPHI进行基于XML的WEB应用开发。起初的设想是很美好的,但结果做出来的东西很简陋。一部分原因就在于XML到Object之间的数据绑定实现太麻烦(另一部分是因为对XSLT不熟,学习它花了很多时间)。

    之前我一直是用DELPHI提供的XML Data binding来做的,基本做法是:先用工具(如XMLSPY)做好一个XML Schema(XSD),然后用XML Data binding生成DELPHI的接口和类。当然,一旦生成好就很方便了,在程序里我只要操作这个接口就好了,其中各个Field都会被变成属性,并且类型也都如我在XSD中的定义。但问题在于程序在开发过程中,总是会有一些变化的,在这种情况下,我就不得不同时开着XMLSPY修改XSD,然后重新用XML Data binding的Wizard跑一遍,非常的麻烦。

    所以当我想到数据集的对象化后,立即想到也可以用RTTI来实现Object的XML持久化–其实DELPHI6开始的SOAP实现就是用RTTI来实现Object到SOAP数据(就是XML)的转换的。显然我已经是非常的后知后觉了,当我在《强大的DELPHI RTTI–兼谈需要了解多种开发语言》一文中说到我的打算时,朋友Lex CHow回复我说他在大约一年前就做过了这方面的工作,我当即跟他要来了他的源码。lexlib是他写的是一个有很多功能的库,看上去结构有点像.net的基本类库(当然没那么大^O^),Object的XML持久化只是其中的很小的一部分。因为我只需要这一部分,就没必要用这整个一个库这么麻烦,于是参考了lexlib并结合我在《用DELPHI的RTTI实现数据集的简单对象化》中已经实现的部分,做了一个简单的实现:

TMXMLPersistent = class(TObject)publicclass Procedure LoadObjFromXML( aNode : IXMLNode; aObj : TPersistent );class Procedure SaveObjToXML(   aNode : IXMLNode; aObj : TPersistent );end;constDefaultFilter : TTypeKinds = [tkInteger, tkChar, tkEnumeration,tkFloat, tkString, tkSet, tkWChar, tkLString, tkWString, tkInt64];{ TMXMLPersistent }class procedure TMXMLPersistent.LoadObjFromXML(aNode: IXMLNode;aObj: TPersistent);Vari : Integer;pList : TMPropList;pInfo : PPropInfo;tmpObj: TObject;beginIf ( aObj Is TMDataSetProxy ) Then( aObj As TMDataSetProxy ).LoadFromXML( aNode )ElseBeginpList := TMPropList.Create( aObj );TryFor i := 0 To pList.PropCount - 1 DoBeginpInfo := pList.Props[i];If ( pInfo^.PropType^.Kind = tkClass ) ThenBegintmpObj := TObject( Integer( GetPropValue( aObj, pInfo^.Name ) ) );If ( Assigned( tmpObj ) AND ( tmpObj Is TPersistent ) ) ThenLoadObjFromXML( aNode.ChildNodes[WideString(pInfo^.Name)],tmpObj As TPersistent );EndElse If ( pInfo^.PropType^.Kind In DefaultFilter ) ThenSetPropValue( aObj, pInfo^.Name,String( aNode.ChildNodes[WideString( pInfo^.Name )].Text ) );End;FinallypList.Free;End;End;end;class procedure TMXMLPersistent.SaveObjToXML(aNode: IXMLNode;aObj: TPersistent);Vari : Integer;pList : TMPropList;pInfo : PPropInfo;tmpObj: TObject;beginIf ( aObj Is TMDataSetProxy ) Then( aObj As TMDataSetProxy ).SaveToXML( aNode )ElseBeginpList := TMPropList.Create( aObj );TryFor i := 0 To pList.PropCount - 1 DoBeginpInfo := pList.Props[i];If ( pInfo^.PropType^.Kind = tkClass ) ThenBegintmpObj := TObject( Integer( GetPropValue( aObj, pInfo^.Name ) ) );If ( Assigned( tmpObj ) AND ( tmpObj Is TPersistent ) ) ThenSaveObjToXML( aNode.AddChild( WideString( pInfo^.Name ) ),tmpObj As TPersistent );EndElse If ( pInfo^.PropType^.Kind In DefaultFilter ) ThenaNode.AddChild( WideString( pInfo^.Name ) ).Text :=GetPropValue( aObj, pInfo^.Name );End;FinallypList.Free;End;End;end;

    这个实现应该说是很简单的。主要是三个部分(Load和Save的结构是相似的):

    一是对TMDataSetProxy作特别处理,委托给这个类自己去处理它的实现,因为它与一般的类不同,需要通过ForEach遍历全部记录,这其实就是同时实现数据集的XML持久化。

    二是对Class作递归处理,当然只支持从TPersistent派生的class。

    三是一般的Field简单地转成String保存,其中借鉴了lexlib的Filter,只处理那些能简单地转成String的数据类型,过滤掉那些可能造成转换出错的类型。

    上面的代码中用到的TMPropList见《用DELPHI的RTTI实现数据集的简单对象化》中的实现。

    下面是用TMDataSetProxy实现的数据集的XML持久化。免去了需要通过TClientDataSet进行的麻烦,并且采用的是用Node记录字段的方式,.net也是采用这样的方式,与TClientDataSet所用的用Attribute记录字段的方式不同。虽然这样生成的XML文件将会略大一些,但是好处也是显而易见的,特别是我是准备用在Web应用中的,用Node方式记录的XML,在用XSLT时会方便很多。

procedure TMDataSetProxy.LoadFromXML(aNode: IXMLNode);Vari, j : Integer;pInfo : PPropInfo;pRow  : IXMLNode;beginFor j := 0 To aNode.ChildNodes.Count - 1 DoBeginFDataSet.Append;pRow := aNode.ChildNodes[j];For i := 0 To FPropList.PropCount - 1 DoBeginpInfo := FPropList.Props[i];If ( pInfo^.PropType^.Kind In DefaultFilter ) ThenSetVariant( i,String( pRow.ChildNodes[WideString( pInfo^.Name )].Text ) );End;EndEdit;End;FDataSet.First;end;procedure TMDataSetProxy.SaveToXML(aNode: IXMLNode);Vari : Integer;pInfo : PPropInfo;pRow  : IXMLNode;beginWhile ForEach DoBeginpRow := aNode.AddChild( 'Row' );For i := 0 To FPropList.PropCount - 1 DoBeginpInfo := FPropList.Props[i];If ( pInfo^.PropType^.Kind In DefaultFilter ) ThenpRow.AddChild( WideString( pInfo^.Name ) ).Text:= GetVariant( i );End;End;end;

    下面是一个简单的DEMO,其中包括了对数据集的XML持久化。注意Load的时候Employee成员连接的是ADODataSet2,它连接到一个包含了这几个字段的表,各字段类型与Employee表相同,但内容为空,并且去掉了EmployeeID的Identity。Load完成后,Employee表中这几个字段的内容将被复制到此表中。

TDemoCompany = class( TPersistent )privateFEmployee : TDSPEmployee;FCompany  : String;FCode     : Integer;publishedproperty Employee : TDSPEmployee Read FEmployee Write FEmployee;property Company  : String       Read FCompany  Write FCompany;Property Code     : Integer      Read FCode     Write FCode;End;procedure TForm1.SaveClick(Sender: TObject);Vardemo : TDemoCompany;begindemo := TDemoCompany.Create;demo.Employee := TDSPEmployee.Create( ADODataSet1 );demo.Company  := 'Demo company';demo.Code     := 987654;TryXMLDocument1.Active := true;TMXMLPersistent.SaveObjToXML( XMLDocument1.AddChild( 'Demo' ), demo );XMLDocument1.SaveToFile( 'temp.xml' );XMLDocument1.Active := false;Finallydemo.Employee.Free;demo.Employee := Nil;demo.Free;End;end;procedure TForm1.LoadClick(Sender: TObject);Vardemo : TDemoCompany;begindemo := TDemoCompany.Create;demo.Employee := TDSPEmployee.Create( ADODataSet2 );TryXMLDocument1.Active := true;XMLDocument1.LoadFromFile( 'temp.xml' );TMXMLPersistent.LoadObjFromXML( XMLDocument1.ChildNodes.Last, demo );XMLDocument1.Active := false;Edit1.Text := demo.Company;Edit2.Text := IntToStr( demo.Code );While ( demo.Employee.ForEach ) DoWith ListView1.Items.Add DoBeginCaption := IntToStr( demo.Employee.EmployeeID );SubItems.Add( demo.Employee.FirstName );SubItems.Add( demo.Employee.LastName );SubItems.Add( FormatDateTime( 'yyyy-mm-dd', demo.Employee.BirthDate ) );End;Finallydemo.Employee.Free;demo.Employee := Nil;demo.Free;End;end;

    终于可以告别那个麻烦的XML Data binding了,并且以后也不用写XSD了–虽然有好用的工具,但能省点事终归是好的。

猛禽 Jan.29-05

终于搞定了

花了一天时间把数据集对象化的实现基本完成,并写了一篇东东《用DELPHI的RTTI实现数据集的简单对象化》。

顺便参考了Lex CHow的lexlib,实现了一个Object到XML的持久化。不过文章还没写好。:P

这一个休息天用得还算有价值。

不过刚才发给一个朋友看,却被认为意义不大,存心打击偶嘛。sigh~~~

BTW:XML持久化也完成了《用DELPHI的RTTI实现对象的XML持久化

作弊、英语、就业、整容、游戏……

老方在《关于英语作弊的问题》中提起今年的研究生考试中英语作弊的问题。偶是因为那天要去上体馆跟帮主、色色汇合开本帮常委会,坐在公车上听到乘客们讨论才知道那天是研究生考试,车上的乘客里,除了偶介个FB分子以外,基本都是刚从考场出来的。汗一个。

这种事在这个月已经是第二次发生了,上次便是半个月前的CET考试。又有人通过猫扑泄题了详情)。记得好像去年也是MOP,难怪常有人说现在的MOP已经是小P孩们的天下了。

不过话说回来。考生为什么要作弊?

这不是废话么,比如CET要是没过,学位证就拿不到。

我们对英文的过分要求已经近乎变态了。世界上恐怕很难找出第二国家像我们这样把一门外语看得比母语还重要的,便是殖民国家也不至于这样。就像《英语四六级考试乱象横生 学生汉语水平衰退严重》一文中所说的英语小说翻译大赛的结果那样,绝不是危言耸听。

还有一个问题是没有CET证书、学位证书,工作也不好找。

说到就业压力,昨天一则新闻说最近上海几大整形医院生意兴隆,很多大学生,特别是应届生,纷纷趁寒假之机赶来花大钱大做整形手术。虽然的确有很多用人单位以貌取人,但是作为一个以赢利为目的的企业来说,实力还是最重要的考核标准。正如节目中采访的专家所说:当你选择用整形来改善自己的就业竞争力时,同时也就意味着你在心理上已经放弃了与别人在实力上的竞争,这种做法是不可能达到很好的效果的。

回头来说证书,这其实也是一样的。比如那些曾经很红的MCSE、CCNA之类的证书,现在也不太值钱了。比如前几天给我们公司配路由的那位,好像也是CC口口,一个不算太复杂的路由却配了两天,最后还是找朋友帮忙才搞定。

竞争实力还是来自于平时的学习和积累。求助于作弊可能可以取得一纸证书,但是对竞争力还是没有本质的改善。

那这些作弊的人平时都干什么去了呢?

玩游戏去了……

那我们是不是要打倒游戏?

上周末上视的《1/7》节目采访了陈天桥。记者问起了这个问题,陈天桥说:

这个市场始终是存在的,我不去占领,别人也会去占领。情况不会有任何的不同。而且你们说那些青少年沉迷于游戏夜不归宿荒废学业,那张国荣死的时候还有歌迷也去跳楼的。

虽然我个人对陈天桥没有什么好感,但以这样理由去指责游戏是没有道理的。《网游难道真的猛于虎吗?》一文对这个问题的说法固然不无道理,但是游戏作为娱乐产业中的一项,本来就应该被纳入分级管理中,只是目前国内暂时没有这样的分级制度罢了。

倒是《评论:你找陈天桥干什么》说的更合理一些。重要的还是“如何扶持产业健康发展”的问题。

而在这些问题中嚷嚷得最响的教育界人士们,《评论:网络游戏难道真的是毒蛇猛兽吗》的说法就是给你们的一记响亮的耳光。horse在《道德教育与教育中犹疑》中说到商学院教导学生利润是企业成功的唯一佐证,而忽视了对道德的教育。回头看我们的教育,鲁迅在《死》(《且介亭杂文附集》)中有一段相当于遗嘱的话,其中第七条便是:

七,损着别人的牙眼,却反对报复,主张宽容的人,万勿和他接近。

而在我们伟大的教育产业化中,那些哗哗数钱的人却在教育着孩子们要“道德”。当所有手中有权的人们都在想方设法地为自己或小集体的利益着想时,我们不禁要问:中国教育-谁为你哭泣?

不受监管的权力是不可能不被滥用的。网吧问题就是这样。为什么不取消CET这样的要求?因为它是很多人的摇钱树。为什么总是人有要冒着坐牢的风险去卖考题或当枪手?因为有人为了证书愿意出钱。

唉,都是钱惹的祸。田亮因为只顾赚钱被开除出国家队,郭晶晶又被狗仔队们发现疑似与有钱人家的公子有来往。sigh~~~

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

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

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

select * from Employee

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

With ADODataSet1 DoBeginOpen;While Not Eof DoBeginWith ListView1.Add DoBeginCaption := 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来实现这个简单的数据集对象化方案。下面是实现与传统代码同样功能的数据集对象化应用代码:

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;publicconstructor 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;publicconstructor 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参数是在编译时就确定地传给函数了,并没有一个动态的表来记录,所以只能采用现在这样的方法来将就。

猛禽 Jan.28-05

[技术贴]强大的DELPHI RTTI–兼谈需要了解多种开发语言

风焱在《“18般武艺”?》中说到他碰上的被多种语言纠缠的问题。我在回复里说:
 很多语言只要能看懂几分就行了,没必要每一种都精通
但是如果只会很少的一两种语言也是不行的。

因为看了一些关于JAVA的反射技术的应用,忽然想到DELPHI的RTTI也很强,于是试着拿数据集下手,用RTTI来实现它的对象化。用了两个晚上时间就搞定了(要不是因为开始时搞错对象–基类用了TObject,其实应该是用TPersistent才对),果然很简单。

假设有一个ADODataSet控件,连接罗斯文数据库,SQL为:

select * from Employee

现在要把它的内容中EmployeeID, FirstName, LastName三个字段显示到ListView里。我通过RTTI实现了一个数据集代理类,使得代码得到大大的简化(这两天争取把结果整理出来另外撰文说明)。其结果大致如下:

TypeTPDSEmployee = class( TMProxyDataSet )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;End;...emp := TPDSEmployee.Create( ADODataSet1 );While emp.ForEach DoWith ListView1.Add DoBeginCaption := IntToStr( emp.EmployeeID );SubItems.Add( emp.FirstName );SubItems.Add( emp.LastName );End;emp.Free;

对比传统的实现代码,好处是显而易见的。

但是当我实现出这个TMProxyDataSet类后,不禁感到痛心疾首,这个我早在三年前就应该想到的。

三年前DELPHI6刚推出时,我就发现它的SOAP功能是通过DELPHI强大的RTTI来实现的,我为什么当时没有想到去深入研究一下DELPHI的RTTI呢?

这次要不是因为看到了一些JAVA的资料,我可能还是想不到,所以多了解一些别的语言是很重要的事。特别是最近以来,动态语言越来受到关注,虽然它们在性能上不能跟原生开发相比,但在很多的开发思想上,具有重要的启发意义。

在做了这个东东以后,我才意识到,DELPHI其实是所有原生开发语言中,动态性最高的,并不比基于虚拟机的JAVA和C#低多少。只是长期在做RAD的开发,没有体会到而已。程序员在RAD下被惯坏了。

做完这个,我打算下一步再试试用RTTI实现对象的XML持久化(基本上就是抄袭一下DELPHI本身的SOAP实现代码-_-|||)。这个思路应该会比我原先用的XML Data Binding要方便很多,至少不用再去写那个麻烦的XML Schema了。

BTW:以前没有太关注RTTI,效率恐怕是其中最重要的一个原因,但是现在看来,跟虚拟机语言甚至动态语言相比,DELPHI作为原生应用开发,这点RTTI效率损失其实根本没有想像中那么大的影响。换来开发效率的大大提高还是很值得的。

BTW:抄了令狐的一段CSS(<pre>)。^O^

草根–另一种精英

智识在《Grassroots是“草根”吗?》一文中对“草根”一词咬文嚼字了一番。看了以后恍然大悟,敢情偶们自诩为“草根”,闹了半天原来不是。最多只能是相对于方博士之类的精英人士来说,更加“草根”一些罢了。鹤冲天在《“草根”与 blog》对“草根”一词作了一个调查,发现它是在最近三个月里忽然流行起来的。这也说明了它在很大程度上是一件被用来与商业抄袭者对抗的武器。

坦白说,在两周前mblogger当机事故之前,我几乎没有去看过mblogger以外的Blog。就如Qenghis的《Blog的分类聚合效应》》所说,我就是被聚合在mblogger这个圈子里。当我被迫从这里走出去以后,才发现外面有一个热闹的“草根”VS“精英”的世界。这事是以方博士为代表的少数人企图在Blog世界里制造话语霸权开始的,而以“草根”们为代表的多数人则喊出了“我不是博客”的口号。

那么这个被双方所争权的“话语权”又是什么?

KESO在《骂并被骂着》中所说:

中国人对自由,尤其是言论自由的理解,基本上就是骂人的自由。

对于网上骂战,我在BBS上–比如CSDN的水园–就常可以看到,双方或多方互扔板砖,场面壮观。而对于BLOG的了解也差不多是从骂战开始的:去年刚开始写BLOG时,便听闻不久前博客堂与CoDelphi的对骂。这次事件的后果就是使很多人对部分打着MVP金字招牌的人的RP产生怀疑。

骂人–特别是在网络上–一向是一件很“民主”的事情,这可真是人多力量大,少数人不敢不服从多数。还是Qenghis,在《话语权中的多数人暴政》中说到:

“多数人”并不一定是最理智最正确的人,而多数人一旦就某个问题达成错误的共识,这一共识却极有可能是控制性的。

Tinyfool在后面的回复里也引用了已故经济学家杨小凯关于民主和共和的说法,来证实他的说法。

我在《BC, BD及其它》中曾经说过:精英们垄断BLOG话语权的企图是注定要失败的,他们可以强抄走文字,但话语权还是在文字背后的Bloggers手里。

只是现在看来,这种掌握在草根们手里的话语权未免有些被滥用的嫌疑,比如那种煞有介事地宣称”我不是博客”。Horse说,如果一定要给blog一个中文名,请称呼它为“网志”,我觉得是没有必要。对于Blogger来说,对于什么是Blog,大家心里有数,叫什么中文名无关紧要。愿意叫“网志”还是“博客”是人家的自由,并不是说用了“博客”这个词,就成了方某的Fans。而对于没有实践过BLOG的人来说,跟他们讲BLOG则纯属“对牛乱弹琴”

回顾我最近一两周的BLOG,和以前相比,明显受到草根领袖们的影响,越来越热衷于“热门话题”的讨论。但当我加入这些讨论,成为草根们中的一根后,却忽然发现,其实在对话语权的争夺上,我们与精英们没有什么不同。

重新审视我走出mblogger之前的时光,在BLOG世界中,除了对战的“精英”和“草根”之外,还有为数更多的Blogger们,只是在默默地耕耘着自己的一亩三分地,并不在意于自己的言论造成了多大的影响。

话语权其实就素那浮云。

把视线放到BLOG以外,这个世界上比“草根”们更没有话语权的人还有很多很多。孙志刚之前难道就没有类似的事情发生过吗?只是那些人和他们的亲朋好友都是连“草根”都不是的“泥土”。

我不是博客,也不是Blogger,只是一个八卦的logger–8logger

BTW:写介种东东比较容易招骂。:P
8过偶们应该在在骂声中茁壮成长嘛。
这些年来,网上骂人的水准还是没有很大的长进,大多数人还是只会人身攻击一种招数,我在CSDN发的文档有几篇的回复里多得我都懒得看,乏味得很。