简单性是这个世界上最难获得的东西:它是经验的最终界限,也是天才的最终努力目标。--George Sand
您已经是一位熟练的Delphi程序员,可以运用Delphi快速地写出一个漂亮、实用的程序;您热爱Delphi;她已经成了您工作、学习中不可或缺的一部分。我假设这些都为真,那么您当初选择Delphi作为自己的首选开发工具一定有自己的理由或者原因。
至少,我自己是符合以上的所有假设的。现在,我所想和您分享的,正是我选择Delphi的理由及原因,以及我对Delphi的认识。您可以把我看作一个拥护Delphi的狂热分子,虽然那样会让我感到您把我看得太过肤浅,我并不承认,但是我不介意。因为,我真的热爱她。
第一次接触的Delphi的版本是3.0,那时候只不过把它当作Visual Basic一样的RAD工具来用而已。但是,随着时间的流逝,Delphi 3、Delphi 4、Delphi 5、Delphi 6以及Kylix,对Delphi的认识也越来越深。它是有着丰富内涵的工具,让人对她越了解,就越对她迷恋,越感觉离不开她,虽然它也还只是工具。
Pascal是一种讲究程序美学的语言--毫无疑问,Pascal代码是最优美的代码--基于Object Pascal(一种支持面向对象的Pascal语言)的Delphi让这种美达到了极至。
现在,你可以打开Delphi,选择“Help”-“About”菜单,出现About窗口后,按住Alt键,同时顺序键入“team”,你看到了什么?是的,Delphi开发人员名单,让我们感谢他们制造出如此的更象艺术品的开发工具吧!
1.1 开发工具“以人为本”论
经常可以在各个编程论坛上看到类似这样的问题:“VB还有没有前途?”;“Delphi是不是要淘汰了?”;“MFC是不是要被.NET取代了?”……其实,这些问题在被提出的当时,是没有人能给出答案的。因为一种技术、一个产品的前途,并不完全由其本身所能左右,还与市场需求、出品公司的发展方向等因素有关。而我们所应该关注的,是否就是这些问题的答案呢?我认为不是。
我们知道,世间万物由原子组成;千变万化的程序归根结底由顺序、循环、分支三种结构构成;无论VC的MFC,还是Delphi的VCL,都是由面向对象技术构建的(暂且不论其面向对象的程度)。当你拨开事物表面的表象后,你看到的,是相同的或近似的本质!而掌握了本质之后,就会发现表象的表现形式是那么的理所当然。试想,当你能象侯捷(《深入浅出MFC》的作者)那样把MFC剥得体无完肤,你还会担心MFC被某某框架所取代吗?从这个角度来说,对于一名专业程序员,编程的理念是万变不离其宗的。发现问题、分析问题、解决问题的过程是存在着某种模式的,当你掌握了这种模式后,不同的编程语言,不同的开发环境对你来说,是有共通之处的。
我认为C++是每个专业程序员所必须掌握的。当然,并不是说单纯学习其语法(甚至可以忽略一些语法的学习),而是通过C++学习面向对象的设计、编程方法。因为C++博大精深,因为C++无所不及。在C++中,你可以学习到面向对象理论的全部,学习之后,你会被C++所改造。因为在面向对象理论中存在的,但有所争议的特性(比如:多重继承)在C++中都得到支持。你只有在掌握之后,才可能作出自己的选择(支持或反对)。在掌握了面向对象的理论之后,无论C++、Object Pascal或是Java乃至C#,你会感觉到它们的异曲同工之处。
那是否就是说开发工具(或许应该称为集成开发环境,不过下文还是按我的习惯,用开发工具来称呼)之间除了支持的语言不同外,不存在其他差异了?当然也不是。开发工具是帮助你实现你的理念的工具,也就是构建在基础理念上的上层建筑。开发工具对于你所要实现的理念的支持程度以及对实现过程的简化程度,就是开发工具的体贴度了。开发工具于程序员,犹如兵器于士兵,兵器不顺手,未战先败一半。
一直很喜欢诺基亚手机的广告词:科技以人为本!是的,“人”才是本,工具的使命是辅助人更快、更容易地达到目的。因此,开发工具也应该以人为本!
作为一名程序员,作为开发工具的最直接的使用者,我希望我所使用的开发工具真正是我的伙伴、助手,它能给我带来自由的感觉,让我自由地在代码的世界中驰骋,它能迁就我、适应我,而不是相反,给我套上枷锁!
如今在Windows平台上,有许许多多的开发工具可以选择:Visual C++、Visual Basic、Delphi、C++ Builder、JBuilder……它们基于不同的编程语言、忠于不同的公司的产品理念,从这个角度来说,它们之间的差异是非常大的。
那什么样的开发工具才是优秀的、体贴的、以人为本的?我的标准是符合以下四点:
1、能够将要解决的问题简化,并以某种理念快速实现之
2、不隐藏任何你想知道的细节
3、可以忽略你所不想知道的细节
4、主动去适应不同层次的程序员
符合以上四点的开发工具有吗?我的答案是:有!那就是Delphi!她将一切化繁为简,却从不阻止我寻求真实。你可以在她给你构造的简化了的VCL的虚拟世界中完成任务。也可以钻进VCL的世界以探询她和现实世界(即Windows平台的真实接口)的映射关系,学习它的Framework的设计。你还可以扩展那个虚拟的VCL世界以适应自己的需要。
我为存在着这样的开发工具而感到幸运,更为幸运的是,我可以选择她,和她一起完成我的工作!(现实中,项目中使用什么编程语言、开发工具,时常并不是你个人所能左右的,会受很多因素制约。比如:客户的硬件环境、操作系统环境,开发环境,开发工具的成本、许可证等等。因此能选择自己喜欢的开发工具进行开发工作实在是很幸运的了。)
通过C++学习面向对象的理念,用Delphi去解决现实世界的问题,这是我的做法。同时也验证了那句话:学从难处学,用从易处用。
真正的程序员用C++,聪明的程序员用Delphi。那么,真正聪明的程序员用C++来理解Delphi!
1.2 Delphi更多的优势
用过很多的主流开发工具,为什么还是选择了Delphi?也许是因为没有深入地去熟悉其它开发工具吧,但Delphi本身的优秀至少是原因之一!Delphi优秀在何处?
n 开发的高效
Delphi是一个RAD(Rapid Application Development 快速开发工具),它有可视化的开发环境,当然具有类似功能的开发工具也不少(如Visual Basic),但Delphi有如下的独到之处:
1)Delphi是真正面向对象的。其基于OO技术构建的VCL库中的所有组件都可以被继承以创建新的组件,包括窗体类TForm。相比之下,ActiveX组件缺乏这种灵活性。
2)Delphi的CodeInsight技术(即代码自动完成功能)是建立在编译器信息上的,而VB使用的是类型库信息,使用编译器信息的好处是更具灵活性。不过,时常有程序员抱怨Delphi的代码提示时间太长。其实,我个人感觉是习惯了其速度之后,能体会到一种节奏的快感。
n 语言的高效
Delphi基于Object Pascal语言。这是一种真正支持面向对象而又优雅美观的语言。其在功能的健全上毫不逊色于各种其它的面向对象的语言,但同时又不贪多,盲目地增加复杂性。使得开发者运用各种模式进行设计时都能得到完善的支持,实现时却不用考虑太多语言/编译器细节。
n 编译的高效
可以说,Delphi是Windows平台上最快的高级语言本地代码编译器了。编译速度快有什么好处呢?快速的编译器可以让你频繁地在修改代码和编译运行的状态间切换。至少,我自己已经非常习惯了这样的工作方式:运行程序看一下效果,退出程序修改少量代码再运行程序。而Delphi的编译器从来不会让我有等待的感觉。
n 执行的高效
Delphi不但编译速度快,生成的目标代码的执行效率也非常高。Delphi与C++Builder使用的是同一个后端优化器,因此其生成的代码的效率与优秀的C++编译器生成的代码相同。
Delphi生成完全本地代码,因此Delphi编译结果的可执行文件可以被独立执行、分发(对于“绿色软件”的开发,这一点十分重要)。不需要其他运行库支持。当然,你也可以选择动态链接编译,这样可以大大减小可执行文件的长度,不过这种情况下在分发程序时,必须同时分发必要的运行库文件。
n 维护的高效
C++把许多决策权给了程序员,因此功能十分强大,但同时,要用C++写出出色的面向对象的代码,就要求程序员具有一定的素质。而Delphi程序员会在一定程度上被限制在VCL提供的框架中(当然,完全可以在Delphi中摆脱VCL编程),相对来说,更容易建立良好设计的代码。而Visual Basic则根本没有提供面向对象的编程机制(VB6.0及先前版本都是基于对象,而非面向对象)。代码框架的优良使得软件维护成本大大降低。
基于以上所有理由,我选择Delphi!
1.3 本书主题
我们平时都会写很多代码,为公司,为自己或者为朋友。有时,为了验证自己的一个想法,或学习某一个技术,会写一些试验性的代码。这样的代码的生命周期很短,基本不需要维护,随意写一下就可以。但是,当你真正要完成一个项目的时候,代码设计就非常重要。因为这样的代码是需要长期维护,不断修改或增强的。设计凌乱的代码会使得维护非常困难或者根本不可能,修改这样的代码意味着产生更多的 bug 或者就是灾难。
因此,代码在被编写之前,需要先被设计。这里所说的设计并不是指功能设计,而是指程序员在编码前先对代码框架的设计,以使得今后代码更容易被理解、被维护。
经常听到一种说法:程序员的程序寿命只有35岁。我却从不相信程序员的寿命只到35岁,也许35岁以后,实现能力(其实就是工匠能力)有下降的可能,而设计能力是随着经验的增加不降反升的。这才是最宝贵的。
国外的软件开发小组,一般的骨干都是40岁上下的人,那些才是大师级的程序员,而所谓的过了35岁就不能当程序员的程序员根本没有资格被称为程序员。
软件工程要将程序员变成编码员,变成流水线上的一环,设计工作由专门的设计师完成(如框架设计师)。也许,分工细化是趋势,但是,我们是满足于做编码员还是希望成长为设计师,取决于我们的眼光及努力。
放开眼光,而不是将自己局限于、沉迷于“实现高手”。实现能力是基础,有一定的实现能力才可能成长,但是,它只是必要条件,而不是充分的。否则,就象爬到山腰就以为自己到了山顶,停滞不前了。那么,你只可能是编码员,你的程序寿命也只到35岁。在有了这样的眼光之后,希望本书可以助您起步。
国内出版的Delphi相关书籍,基本都是讲解实现的。本书的书名是《Delphi高手突破》。那么,假设你现在已经是Delphi高手了。所以本书不会涉及太多的实现技巧,虽然也有实例代码,但重点在于其构架的设计,而并非实现。
至此,您也许已经猜出本书的主题了:如何在Delphi中使用面向对象技术,构建良好设计的代码。
在我看来,写代码是艺术创作。优雅的代码可以自解释,而不需要过多的注释。当注释过多的时候,就该考虑设计是否合理了。写书应该也是艺术创作,如果能把自己的认识、经验艺术地告诉读者,而不需要过多的“注释”(浪费篇幅的废话),就非常成功了。我希望自己能做到,至少尽量吧。
1.4 小结
我相信,走上编程这条路,对于我来说是必然的。能成为专业程序员,也是我所梦想并实现了的。但是,Delphi的出现以及被我所认识、熟悉、迷恋并成为工作的一部分,应该说是一个意外的惊喜。
在此,我所想说的就是,对于自己的坚持,就更坚持一些吧。当你清醒地定位了自己之后,清楚地知道自己所选择的道路之后,就不用有所疑问、有所顾忌了,坚持走下去。最终虽然未必会成功(当然,每个人对成功的定义是不一样的),但爱我所爱,无怨无悔!
在Object Pascal中,所有对象都被建立在内存的堆空间上,而非栈上,因此构造函数不会如同C++那样被编译器自动调用。构造对象和析构对象都是程序员的职责。
构造对象首先要为对象分配内存,这个步骤在Object Pascal中是由编译器支持完成的--即所谓的"编译器魔法(Compile Magic)",此过程程序员不必参与;接着要初始化对象的数据成员,编译器会负责"清零",但如果有特殊的赋值,可以在构造函数中完成;对象在被析构的时侯需要释放所申请的资源(非对象本身所占用内存),这些工作是析构函数的职责;对象本身所占内存的回收,同样由"编译器魔法"完成。
对象内存的分配及回收
编译器在为对象分配内存时,所提供的支持就是在调用构造函数之前插入这几行汇编代码:
test dl, dl
jz +$08
add esp, -$10
call @ClassCreate // 注意这行代码
以上代码的最后一行代码调用的是system.pas文件的第8949行的_ClassCreate函数(以Delphi 6为准),该函数具体为每个对象分配合适的内存。内存分配完成后是调用类的构造函数以初始化数据成员。之后,编译器会再插入以下几行汇编代码:
test dl, dl
jz +{logcontent}f
call @AfterConstruction
pop dword ptr fs:[{logcontent}]
add esp, {logcontent}c
其中主要工作是调用每个对象实例的AfterConstruction,这个调用在Delphi中没有用,它的存在是为C++Builder所保留的。
同样,析构对象时,首先要调用类的析构函数以释放对象申请的资源。之后是回收对象本身所占内存空间,这件工作是由编译器在调用析构函数后,插入以下的汇编代码来完成的:
call @BeforeDestruction
test dl, dl
jle +$05
call @ClassDestroy
这些代码所做的工作与构造对象分配内存时所做的是对应的,主要是对system.pas中第8997行的_ClassDestroy函数的调用。
构造函数与析构函数
定义构造函数使用Constructor关键字,按惯例,构造函数名称为Create(当然也可以用其他名称,但那绝非优良的设计!)。如:
type
TMyFamily = class // 为你的家庭定义的类
Private
FMyFatherName : String; // 你父亲的名字
FMyMotherName : String; // 你母亲的名字
…… // 你家庭中的其他成员
Public
Constructor Create(strFatherName, strMotherName : String);
…… // 其它方法
End;
也许你会问,如果我没有为我的类提供构造函数,它的对象能否被建立呢?答案是:可以。原因前面已经说了,对象本身所占内存的分配是由编译器完成的。而且由于Object Pascal中,所有类(除了TObject类本身)都是从TObject类派生,因此编译器会调用TObject.Create()构造函数,只是这个函数是一个空函数,它并不会对TMyFamily类的数据成员(FMyFatherName、FMyMotherName)初始化,它们会被自动清为空字符串(即''),因为TObject.Create()根本就不认识你的父、母亲!
创建对象时则直接调用构造函数,形式如下:
MyFamilyObject := TMyFamily.Create('Zhang', 'Li');
定义析构函数使用Destructor关键字,按惯例,析构函数名称为Destroy。如:
type
TMyClass = class
Public
Destructor Destroy(); override;
End;
之所以在析构函数声明最后加上override声明,是因为保证在多态的情况下对象能正确被析构(关于多态,将在2.4节中详述)。如果不加override关键字,编译器会给出类似"Method 'Destroy' hides virtual method of base type 'TObject'"的警告提示。警告的意思是你定义的Destroy隐藏了基类的虚方法TObject.Destroy(),那样的话,在多态的情况下就无法正确析构对象了。
注意:析构函数都需要加override声明。
同样,如果在你的类中没有特殊的资源需要被释放,那么你也可以不定义析构函数。只是,在析构对象的时候,应该调用对象的Free()方法而不是直接调用Destroy()。
MyFamilyObject.Free();
这是因为在Free()方法中会判断对象本身是否为nil,如果不为nil才调用对象的Destroy(),以增加安全性。既然有这样的更安全的做法,当然没有理由不这么做了。
注意:永远不要直接调用对象的Destroy(),而应该是Free()。
由此可以得出结论,在Object Pascal中你只需关注对象所申请的资源的分配与释放,而不必关心对象本身所占空间!
创建良好设计的代码(基于Delphi/VCL)-- 2001.6.6
我们平时都会写很多代码,为公司,为自己或者为朋友。有时,为了验证自己的一个想法,或学习某一个技术,会写一些试验性的代码。这样的代码的生命周期很短,基本不需要维护,随意写一下就可以。但是,当你真正要完成一个 Project 的时候,代码设计就非常重要。因为这样的代码是需要长期维护,不断修改或增强的。设计凌乱的代码会使得维护非常困难或者根本不可能,修改这样的代码意味着产生更多的 bug 或者就是灾难。
既然,代码设计如此重要,我们就不能忽视它。那么,如何设计代码呢?面向对象编程技术可以帮助我们。在此,插一些题外话,很多程序员将面向对象编程(OOP)技术和面向对象(OO)技术混淆。就我自己的认识来说,面向对象技术是一门博大精深的学问,它是一种方法论或者说是一种世界观,而面向对象编程技术只是提供一种在编码时运用面向对象的一种方法。
以下是笔者看了一些相关的书籍和在日常实践中得出的一些体会,希望和大家分享一下。
首先,让界面代码和功能代码分离。需要牢记的一个原则,就是不要将复杂的功能逻辑写在界面代码中。界面窗体的实现文件只用来存放界面代码,而将复杂的功能代码独立出来。举个简单的例子, 假设要从某处获得一个字符串列表,然后显示于 TListBox 中,这样的代码就是值得尊敬的:
ObjectXXX := TObjectXXX.Create;
ListBox1.Items := ObjectXXX.GetStringList;
ObjectXXX.Free;
这样就把获得该字符串列表的复杂逻辑封装于 TObjectXXX 类的实现代码中,并且可以将这个类的定义及实现独立的放在一个 .pas 文件中,以便于维护。将界面代码和功能代码分离还有另外一个好处,一个功能的实现代码可能会被多个界面模块调用,如果将功能实现代码在需要的地方就复制一份,那么你就会有多个相同的模块需要维护,如果需要修改的话,呵呵,几乎不可能保证你不会出错。
其次,让每个模块的逻辑尽可能简单。经验告诉我们,对于过于复杂的逻辑,会给人的理解带来困难。所以,尽可能的让每个模块的代码简单,一般不要超过 25行代码。当你发现你写的逻辑正在趋于复杂,那么这个时候是你寻找对象的时机了,看看能不能将其中的一些逻辑独立出来。
最后,就是要注意变量的命名。经常查看 VCL 源代码,你会发现,VCL类中私有成员变量都以“F”打头,类名都以“T”打头等等这样的规则。这样有什么好处?当别人查看这样的代码时,一旦看到“F”开头的,就立刻可以知道它是该类的私有成员,便于代码的维护。
您已经是一位熟练的Delphi程序员,可以运用Delphi快速地写出一个漂亮、实用的程序;您热爱Delphi;她已经成了您工作、学习中不可或缺的一部分。我假设这些都为真,那么您当初选择Delphi作为自己的首选开发工具一定有自己的理由或者原因。
至少,我自己是符合以上的所有假设的。现在,我所想和您分享的,正是我选择Delphi的理由及原因,以及我对Delphi的认识。您可以把我看作一个拥护Delphi的狂热分子,虽然那样会让我感到您把我看得太过肤浅,我并不承认,但是我不介意。因为,我真的热爱她。
第一次接触的Delphi的版本是3.0,那时候只不过把它当作Visual Basic一样的RAD工具来用而已。但是,随着时间的流逝,Delphi 3、Delphi 4、Delphi 5、Delphi 6以及Kylix,对Delphi的认识也越来越深。它是有着丰富内涵的工具,让人对她越了解,就越对她迷恋,越感觉离不开她,虽然它也还只是工具。
Pascal是一种讲究程序美学的语言--毫无疑问,Pascal代码是最优美的代码--基于Object Pascal(一种支持面向对象的Pascal语言)的Delphi让这种美达到了极至。
现在,你可以打开Delphi,选择“Help”-“About”菜单,出现About窗口后,按住Alt键,同时顺序键入“team”,你看到了什么?是的,Delphi开发人员名单,让我们感谢他们制造出如此的更象艺术品的开发工具吧!
1.1 开发工具“以人为本”论
经常可以在各个编程论坛上看到类似这样的问题:“VB还有没有前途?”;“Delphi是不是要淘汰了?”;“MFC是不是要被.NET取代了?”……其实,这些问题在被提出的当时,是没有人能给出答案的。因为一种技术、一个产品的前途,并不完全由其本身所能左右,还与市场需求、出品公司的发展方向等因素有关。而我们所应该关注的,是否就是这些问题的答案呢?我认为不是。
我们知道,世间万物由原子组成;千变万化的程序归根结底由顺序、循环、分支三种结构构成;无论VC的MFC,还是Delphi的VCL,都是由面向对象技术构建的(暂且不论其面向对象的程度)。当你拨开事物表面的表象后,你看到的,是相同的或近似的本质!而掌握了本质之后,就会发现表象的表现形式是那么的理所当然。试想,当你能象侯捷(《深入浅出MFC》的作者)那样把MFC剥得体无完肤,你还会担心MFC被某某框架所取代吗?从这个角度来说,对于一名专业程序员,编程的理念是万变不离其宗的。发现问题、分析问题、解决问题的过程是存在着某种模式的,当你掌握了这种模式后,不同的编程语言,不同的开发环境对你来说,是有共通之处的。
我认为C++是每个专业程序员所必须掌握的。当然,并不是说单纯学习其语法(甚至可以忽略一些语法的学习),而是通过C++学习面向对象的设计、编程方法。因为C++博大精深,因为C++无所不及。在C++中,你可以学习到面向对象理论的全部,学习之后,你会被C++所改造。因为在面向对象理论中存在的,但有所争议的特性(比如:多重继承)在C++中都得到支持。你只有在掌握之后,才可能作出自己的选择(支持或反对)。在掌握了面向对象的理论之后,无论C++、Object Pascal或是Java乃至C#,你会感觉到它们的异曲同工之处。
那是否就是说开发工具(或许应该称为集成开发环境,不过下文还是按我的习惯,用开发工具来称呼)之间除了支持的语言不同外,不存在其他差异了?当然也不是。开发工具是帮助你实现你的理念的工具,也就是构建在基础理念上的上层建筑。开发工具对于你所要实现的理念的支持程度以及对实现过程的简化程度,就是开发工具的体贴度了。开发工具于程序员,犹如兵器于士兵,兵器不顺手,未战先败一半。
一直很喜欢诺基亚手机的广告词:科技以人为本!是的,“人”才是本,工具的使命是辅助人更快、更容易地达到目的。因此,开发工具也应该以人为本!
作为一名程序员,作为开发工具的最直接的使用者,我希望我所使用的开发工具真正是我的伙伴、助手,它能给我带来自由的感觉,让我自由地在代码的世界中驰骋,它能迁就我、适应我,而不是相反,给我套上枷锁!
如今在Windows平台上,有许许多多的开发工具可以选择:Visual C++、Visual Basic、Delphi、C++ Builder、JBuilder……它们基于不同的编程语言、忠于不同的公司的产品理念,从这个角度来说,它们之间的差异是非常大的。
那什么样的开发工具才是优秀的、体贴的、以人为本的?我的标准是符合以下四点:
1、能够将要解决的问题简化,并以某种理念快速实现之
2、不隐藏任何你想知道的细节
3、可以忽略你所不想知道的细节
4、主动去适应不同层次的程序员
符合以上四点的开发工具有吗?我的答案是:有!那就是Delphi!她将一切化繁为简,却从不阻止我寻求真实。你可以在她给你构造的简化了的VCL的虚拟世界中完成任务。也可以钻进VCL的世界以探询她和现实世界(即Windows平台的真实接口)的映射关系,学习它的Framework的设计。你还可以扩展那个虚拟的VCL世界以适应自己的需要。
我为存在着这样的开发工具而感到幸运,更为幸运的是,我可以选择她,和她一起完成我的工作!(现实中,项目中使用什么编程语言、开发工具,时常并不是你个人所能左右的,会受很多因素制约。比如:客户的硬件环境、操作系统环境,开发环境,开发工具的成本、许可证等等。因此能选择自己喜欢的开发工具进行开发工作实在是很幸运的了。)
通过C++学习面向对象的理念,用Delphi去解决现实世界的问题,这是我的做法。同时也验证了那句话:学从难处学,用从易处用。
真正的程序员用C++,聪明的程序员用Delphi。那么,真正聪明的程序员用C++来理解Delphi!
1.2 Delphi更多的优势
用过很多的主流开发工具,为什么还是选择了Delphi?也许是因为没有深入地去熟悉其它开发工具吧,但Delphi本身的优秀至少是原因之一!Delphi优秀在何处?
n 开发的高效
Delphi是一个RAD(Rapid Application Development 快速开发工具),它有可视化的开发环境,当然具有类似功能的开发工具也不少(如Visual Basic),但Delphi有如下的独到之处:
1)Delphi是真正面向对象的。其基于OO技术构建的VCL库中的所有组件都可以被继承以创建新的组件,包括窗体类TForm。相比之下,ActiveX组件缺乏这种灵活性。
2)Delphi的CodeInsight技术(即代码自动完成功能)是建立在编译器信息上的,而VB使用的是类型库信息,使用编译器信息的好处是更具灵活性。不过,时常有程序员抱怨Delphi的代码提示时间太长。其实,我个人感觉是习惯了其速度之后,能体会到一种节奏的快感。
n 语言的高效
Delphi基于Object Pascal语言。这是一种真正支持面向对象而又优雅美观的语言。其在功能的健全上毫不逊色于各种其它的面向对象的语言,但同时又不贪多,盲目地增加复杂性。使得开发者运用各种模式进行设计时都能得到完善的支持,实现时却不用考虑太多语言/编译器细节。
n 编译的高效
可以说,Delphi是Windows平台上最快的高级语言本地代码编译器了。编译速度快有什么好处呢?快速的编译器可以让你频繁地在修改代码和编译运行的状态间切换。至少,我自己已经非常习惯了这样的工作方式:运行程序看一下效果,退出程序修改少量代码再运行程序。而Delphi的编译器从来不会让我有等待的感觉。
n 执行的高效
Delphi不但编译速度快,生成的目标代码的执行效率也非常高。Delphi与C++Builder使用的是同一个后端优化器,因此其生成的代码的效率与优秀的C++编译器生成的代码相同。
Delphi生成完全本地代码,因此Delphi编译结果的可执行文件可以被独立执行、分发(对于“绿色软件”的开发,这一点十分重要)。不需要其他运行库支持。当然,你也可以选择动态链接编译,这样可以大大减小可执行文件的长度,不过这种情况下在分发程序时,必须同时分发必要的运行库文件。
n 维护的高效
C++把许多决策权给了程序员,因此功能十分强大,但同时,要用C++写出出色的面向对象的代码,就要求程序员具有一定的素质。而Delphi程序员会在一定程度上被限制在VCL提供的框架中(当然,完全可以在Delphi中摆脱VCL编程),相对来说,更容易建立良好设计的代码。而Visual Basic则根本没有提供面向对象的编程机制(VB6.0及先前版本都是基于对象,而非面向对象)。代码框架的优良使得软件维护成本大大降低。
基于以上所有理由,我选择Delphi!
1.3 本书主题
我们平时都会写很多代码,为公司,为自己或者为朋友。有时,为了验证自己的一个想法,或学习某一个技术,会写一些试验性的代码。这样的代码的生命周期很短,基本不需要维护,随意写一下就可以。但是,当你真正要完成一个项目的时候,代码设计就非常重要。因为这样的代码是需要长期维护,不断修改或增强的。设计凌乱的代码会使得维护非常困难或者根本不可能,修改这样的代码意味着产生更多的 bug 或者就是灾难。
因此,代码在被编写之前,需要先被设计。这里所说的设计并不是指功能设计,而是指程序员在编码前先对代码框架的设计,以使得今后代码更容易被理解、被维护。
经常听到一种说法:程序员的程序寿命只有35岁。我却从不相信程序员的寿命只到35岁,也许35岁以后,实现能力(其实就是工匠能力)有下降的可能,而设计能力是随着经验的增加不降反升的。这才是最宝贵的。
国外的软件开发小组,一般的骨干都是40岁上下的人,那些才是大师级的程序员,而所谓的过了35岁就不能当程序员的程序员根本没有资格被称为程序员。
软件工程要将程序员变成编码员,变成流水线上的一环,设计工作由专门的设计师完成(如框架设计师)。也许,分工细化是趋势,但是,我们是满足于做编码员还是希望成长为设计师,取决于我们的眼光及努力。
放开眼光,而不是将自己局限于、沉迷于“实现高手”。实现能力是基础,有一定的实现能力才可能成长,但是,它只是必要条件,而不是充分的。否则,就象爬到山腰就以为自己到了山顶,停滞不前了。那么,你只可能是编码员,你的程序寿命也只到35岁。在有了这样的眼光之后,希望本书可以助您起步。
国内出版的Delphi相关书籍,基本都是讲解实现的。本书的书名是《Delphi高手突破》。那么,假设你现在已经是Delphi高手了。所以本书不会涉及太多的实现技巧,虽然也有实例代码,但重点在于其构架的设计,而并非实现。
至此,您也许已经猜出本书的主题了:如何在Delphi中使用面向对象技术,构建良好设计的代码。
在我看来,写代码是艺术创作。优雅的代码可以自解释,而不需要过多的注释。当注释过多的时候,就该考虑设计是否合理了。写书应该也是艺术创作,如果能把自己的认识、经验艺术地告诉读者,而不需要过多的“注释”(浪费篇幅的废话),就非常成功了。我希望自己能做到,至少尽量吧。
1.4 小结
我相信,走上编程这条路,对于我来说是必然的。能成为专业程序员,也是我所梦想并实现了的。但是,Delphi的出现以及被我所认识、熟悉、迷恋并成为工作的一部分,应该说是一个意外的惊喜。
在此,我所想说的就是,对于自己的坚持,就更坚持一些吧。当你清醒地定位了自己之后,清楚地知道自己所选择的道路之后,就不用有所疑问、有所顾忌了,坚持走下去。最终虽然未必会成功(当然,每个人对成功的定义是不一样的),但爱我所爱,无怨无悔!
在Object Pascal中,所有对象都被建立在内存的堆空间上,而非栈上,因此构造函数不会如同C++那样被编译器自动调用。构造对象和析构对象都是程序员的职责。
构造对象首先要为对象分配内存,这个步骤在Object Pascal中是由编译器支持完成的--即所谓的"编译器魔法(Compile Magic)",此过程程序员不必参与;接着要初始化对象的数据成员,编译器会负责"清零",但如果有特殊的赋值,可以在构造函数中完成;对象在被析构的时侯需要释放所申请的资源(非对象本身所占用内存),这些工作是析构函数的职责;对象本身所占内存的回收,同样由"编译器魔法"完成。
对象内存的分配及回收
编译器在为对象分配内存时,所提供的支持就是在调用构造函数之前插入这几行汇编代码:
test dl, dl
jz +$08
add esp, -$10
call @ClassCreate // 注意这行代码
以上代码的最后一行代码调用的是system.pas文件的第8949行的_ClassCreate函数(以Delphi 6为准),该函数具体为每个对象分配合适的内存。内存分配完成后是调用类的构造函数以初始化数据成员。之后,编译器会再插入以下几行汇编代码:
test dl, dl
jz +{logcontent}f
call @AfterConstruction
pop dword ptr fs:[{logcontent}]
add esp, {logcontent}c
其中主要工作是调用每个对象实例的AfterConstruction,这个调用在Delphi中没有用,它的存在是为C++Builder所保留的。
同样,析构对象时,首先要调用类的析构函数以释放对象申请的资源。之后是回收对象本身所占内存空间,这件工作是由编译器在调用析构函数后,插入以下的汇编代码来完成的:
call @BeforeDestruction
test dl, dl
jle +$05
call @ClassDestroy
这些代码所做的工作与构造对象分配内存时所做的是对应的,主要是对system.pas中第8997行的_ClassDestroy函数的调用。
构造函数与析构函数
定义构造函数使用Constructor关键字,按惯例,构造函数名称为Create(当然也可以用其他名称,但那绝非优良的设计!)。如:
type
TMyFamily = class // 为你的家庭定义的类
Private
FMyFatherName : String; // 你父亲的名字
FMyMotherName : String; // 你母亲的名字
…… // 你家庭中的其他成员
Public
Constructor Create(strFatherName, strMotherName : String);
…… // 其它方法
End;
也许你会问,如果我没有为我的类提供构造函数,它的对象能否被建立呢?答案是:可以。原因前面已经说了,对象本身所占内存的分配是由编译器完成的。而且由于Object Pascal中,所有类(除了TObject类本身)都是从TObject类派生,因此编译器会调用TObject.Create()构造函数,只是这个函数是一个空函数,它并不会对TMyFamily类的数据成员(FMyFatherName、FMyMotherName)初始化,它们会被自动清为空字符串(即''),因为TObject.Create()根本就不认识你的父、母亲!
创建对象时则直接调用构造函数,形式如下:
MyFamilyObject := TMyFamily.Create('Zhang', 'Li');
定义析构函数使用Destructor关键字,按惯例,析构函数名称为Destroy。如:
type
TMyClass = class
Public
Destructor Destroy(); override;
End;
之所以在析构函数声明最后加上override声明,是因为保证在多态的情况下对象能正确被析构(关于多态,将在2.4节中详述)。如果不加override关键字,编译器会给出类似"Method 'Destroy' hides virtual method of base type 'TObject'"的警告提示。警告的意思是你定义的Destroy隐藏了基类的虚方法TObject.Destroy(),那样的话,在多态的情况下就无法正确析构对象了。
注意:析构函数都需要加override声明。
同样,如果在你的类中没有特殊的资源需要被释放,那么你也可以不定义析构函数。只是,在析构对象的时候,应该调用对象的Free()方法而不是直接调用Destroy()。
MyFamilyObject.Free();
这是因为在Free()方法中会判断对象本身是否为nil,如果不为nil才调用对象的Destroy(),以增加安全性。既然有这样的更安全的做法,当然没有理由不这么做了。
注意:永远不要直接调用对象的Destroy(),而应该是Free()。
由此可以得出结论,在Object Pascal中你只需关注对象所申请的资源的分配与释放,而不必关心对象本身所占空间!
创建良好设计的代码(基于Delphi/VCL)-- 2001.6.6
我们平时都会写很多代码,为公司,为自己或者为朋友。有时,为了验证自己的一个想法,或学习某一个技术,会写一些试验性的代码。这样的代码的生命周期很短,基本不需要维护,随意写一下就可以。但是,当你真正要完成一个 Project 的时候,代码设计就非常重要。因为这样的代码是需要长期维护,不断修改或增强的。设计凌乱的代码会使得维护非常困难或者根本不可能,修改这样的代码意味着产生更多的 bug 或者就是灾难。
既然,代码设计如此重要,我们就不能忽视它。那么,如何设计代码呢?面向对象编程技术可以帮助我们。在此,插一些题外话,很多程序员将面向对象编程(OOP)技术和面向对象(OO)技术混淆。就我自己的认识来说,面向对象技术是一门博大精深的学问,它是一种方法论或者说是一种世界观,而面向对象编程技术只是提供一种在编码时运用面向对象的一种方法。
以下是笔者看了一些相关的书籍和在日常实践中得出的一些体会,希望和大家分享一下。
首先,让界面代码和功能代码分离。需要牢记的一个原则,就是不要将复杂的功能逻辑写在界面代码中。界面窗体的实现文件只用来存放界面代码,而将复杂的功能代码独立出来。举个简单的例子, 假设要从某处获得一个字符串列表,然后显示于 TListBox 中,这样的代码就是值得尊敬的:
ObjectXXX := TObjectXXX.Create;
ListBox1.Items := ObjectXXX.GetStringList;
ObjectXXX.Free;
这样就把获得该字符串列表的复杂逻辑封装于 TObjectXXX 类的实现代码中,并且可以将这个类的定义及实现独立的放在一个 .pas 文件中,以便于维护。将界面代码和功能代码分离还有另外一个好处,一个功能的实现代码可能会被多个界面模块调用,如果将功能实现代码在需要的地方就复制一份,那么你就会有多个相同的模块需要维护,如果需要修改的话,呵呵,几乎不可能保证你不会出错。
其次,让每个模块的逻辑尽可能简单。经验告诉我们,对于过于复杂的逻辑,会给人的理解带来困难。所以,尽可能的让每个模块的代码简单,一般不要超过 25行代码。当你发现你写的逻辑正在趋于复杂,那么这个时候是你寻找对象的时机了,看看能不能将其中的一些逻辑独立出来。
最后,就是要注意变量的命名。经常查看 VCL 源代码,你会发现,VCL类中私有成员变量都以“F”打头,类名都以“T”打头等等这样的规则。这样有什么好处?当别人查看这样的代码时,一旦看到“F”开头的,就立刻可以知道它是该类的私有成员,便于代码的维护。
回复Comments
作者:
{commentrecontent}