Object Oriented Programming with Free Pascal and Lazarus/zh CN

From Free Pascal wiki
Revision as of 13:08, 19 June 2015 by FTurtle (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

English (en) español (es) français (fr) magyar (hu) italiano (it) 日本語 (ja) македонски (mk) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

有许多Pascal的优秀教程,但是本教程尝试让初学者走得更远,进入面向对象编程,这是对Turbo-pascal,Delphi和FreePascal/Lazar提供的标准Pascal的扩展。

对象是对标准Pascal record结构的扩展

标准的基于文本的Pascal编程善于创建类似传统Unix应用,做一件事并且做得很好的应用。程序做的“一件事”可能是一个非常复杂的动作,可能给用户提供几个菜单驱动的选项,但是它本质上受限于服从用户在键盘上输入的命令,在终端或打印机上列出它的响应。

为了提供一个图形用户界面(GUI),通常要调用某种形式的面向对象编程方法(经常使用 C或它的变种,或Visual Basic,或诸如与或不与Lazarus一起使用的FreePascal的Pascal变种)。

在GUI里,有大量以结构化方式排列的图片,由与各种诸如下面行为关联的一系列工具或部件组成的屏幕代表用户。

  • 从菜单选择,
  • 打开或保存文件,
  • 连接Internet,
  • 执行数字计算,等等。

预期用户在屏幕上移动鼠标或其它光标/选择工具,选择对应鼠标按钮点击或按键执行的行为。

当处理复杂图形用户界面的系统可以用标准Pascal或大多数任意其它编程语言编写时,那么更容易使用一个面向对象系统,在这样的系统里,屏幕上的每个图形对象可以有以通常结构保存在一起的所有属性,与用法关联的过程和函数。


对象——一个现实世界模拟

设想医院或医生办公室收集的血液样本的模拟。

血液样本

实物样本当然地是一个对象;它有与之关联的许多信息,文档和其它实物对象。

  • 由医生想要实施的测试决定类型的样本试管
  • 指示护士或技术员收集样本的本地规则 (或方法标准操作过程)
    • 使用哪类试管,
    • 如何处理样本
    • 在转移到实验室之前如何存储它。
  • 试管上有下列细节的标签
    • 样本标识
    • 病人姓名和生日
    • 收集日期和时间
    • 需要的测试。
  • 与样本一起到实验室的申请表,表明
    • 样本标识
    • 申请医生的标识
    • 医生申请的测试以及
    • 给出的病人的充分详情
    • 寻求可能诊断的确认。

申请表的拷贝放在病人笔记里,提醒医生在合适时间里得到结果。

  • 在实验室里——本地方法决定
    • 如何分析样本,
    • 使用哪台机器,
    • 机器如何校准和操作,
    • 结果如何存储,格式化以及
    • 返回给医生。

真实的结果是医生用来辅助诊断的纪录,它的拷贝填写在病人笔记里。

为了参考,确认或进一步的测试,实物样本可能被保留;或者可能被冲下洗涤槽或焚化;将有一个方法来描述这。

每次收集样本时,医生不需要阐明所有的细节和指示;确实,他可能没有样本在实验室里如何处理的知识。各种处理的细节是从以前样本收集和分析继承的——整个顺序有一个通用计划,我们可以把血液样本,它的所有文档,数据和底层方法,当作一个复杂的对象

在医生的心里,血液样本看作和它的结果是一样的实体;对护士和技术人员来说,样本,试管,标签和存储条件组成一个单一实体。

另外一个例子——汽车

如果你不喜欢血液,同样的推理顺序可以应用到放到车库里维修的卡车。 它可能包括:

  • 实物车辆
  • 所有者持有的文档:注册或执照(包括车牌),保险,购买收据,部件,维修等等
  • 燃油消耗历史
  • 允许使用车辆的驾驶员,以及执照详情
  • 车库持有的服务纪录
  • 例行检查和维修遵循的方法或过程
  • 非例行维修遵循的方法等等
  • 客户的帐单信息

编程示例

这个预先占领的现实世界例子足够了!(原文:Enough of this pre-occupation with Real-World examples!)让我们前往主要目的:用FreePascal编程。

让我们考虑用FreePascal/Lazarus创建一个有一些控件的一个简单Form的应用。

ObjectInspector-TForm.png
BlankForm.png

调用Lazarus IDE时,程序员面对一个空白的Form设计模版,在其上鼓励他放置各种控件或对象。

注意预置的空白Form已经是一个对象了,有它自己的属性,诸如位置(顶部和右部),大小(高和宽),颜色,添加文本的默认字体等等。



如果放置一个Button控件(TButton类型)在Form上,它将有自己的可以在对象察看窗口查看的一系列属性。

几个属性有与Form的属性类似的名称;这是因为许多属性是从一些通用祖先类继承的,这些祖先类勾画出后代类如何定义和处理属性。

正如属性一样,对象察看器提供了一个称作事件的标签(tab),它可以访问事件处理器,事件处理器是指示应用如何处理诸如鼠标点击按钮(OnClick)或位置、大小或其它属性改变(OnChange)等事件的方法。

Form上Button的图片,与其所有属性和事件处理器方法,在Pascal里应该视为一个单一的实体或对象

ObjectInspector-TButton.png
FormWithButton.png
Source FormWithButton1.png

标准Pascal的面向对象扩展

通过定义

对象

扩展Pascal的纪录结构 对象是特殊类型的纪录。纪录包含在对象定义(就像一个常规纪录)里声明的所有域,但是现在过程和函数可以声明,仿佛它们是纪录的部分,并作为指向与对象类型关联的方法的指针持有。

例如,一个对象可以包含一个实数数组,以及计算平均值的一个方法。

Type
  Average = Object
    NumVal: Integer;
    Values: Array [1..200] of Real;
    Function Mean: Real; { calculates the average value of the array }
  End;

对象可以从”父”对象”继承”域和方法。这意味着可以使用这些域和方法,仿佛它们包含在作为”子”对象声明的对象里。

此外,介绍一个能见性的概念:域,过程和函数可以声明为public,protected或private。默认地,域和方法是public的,可以在当前单元之外输出(exporte)。Protected域和方法仅在从当前祖先对象继承的对象里可见。声明为private的域和方法仅在当前单元可见:它们的范围仅限于当前单元的实现。

在FreePascal和Lazarus里不经常使用对象本身;相反,非常广泛地使用类。类以与对象几乎相同的方式来定义,但它是一个指向对象的指针而不是对象本身。从技术上讲,这意味着在程序堆上分配类,而在堆栈上分配对象。

这里有一个典型类声明的简单例子:

{-----------------------------------------}
{example of Class declaration from the LCL}
{-----------------------------------------}
 TPen = class(TFPCustomPen)
 private
   FColor: TColor;
   FPenHandleCached: boolean;
   FReference: TWSPenReference;
   procedure FreeReference;
   function GetHandle: HPEN;
   function GetReference: TWSPenReference;
   procedure ReferenceNeeded;
   procedure SetHandle(const Value: HPEN);
 protected
   procedure DoAllocateResources; override;
   procedure DoDeAllocateResources; override;
   procedure DoCopyProps(From: TFPCanvasHelper); override;
   procedure SetColor
        (const NewColor: TColor; const NewFPColor: TFPColor); virtual;
   procedure SetFPColor(const AValue: TFPColor); override;
   procedure SetColor(Value: TColor);
   procedure SetMode(Value: TPenMode); override;
   procedure SetStyle(Value: TPenStyle); override;
   procedure SetWidth(value: Integer); override;
 public
   constructor Create; override;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
   property Handle: HPEN read GetHandle write SetHandle; deprecated;
   property Reference: TWSPenReference read GetReference;
 published
   property Color: TColor read FColor write SetColor default clBlack;
   property Mode default pmCopy;
   property Style default psSolid;
   property Width default 1;
 end;


注意该类是作为另一个祖先类(TFPCustomPen)的实例来定义的,它继承了其所有属性和方法。它有一些自己的域,用以下的来分组

  • private - 这意味着这里定义的项目仅对在同一个程序单元里定义的其它类或过程/函数有效或可见(本例来自Graphics,因此在同一单元的诸如TBitMapTPicture等的任意其它类可以使用它们)。它们本质上是本地变量(例如FColorFPenHandleCached)或本地可用方法(GetHandleSetHandle),但是在protectedpublic节定义的项目可以使用或指向它们。
  • protected - 这意味着这里定义的项目仅对该祖先类的后代类有效或可见,并继承它的属性或方法
  • public - 这意味着这里定义的项目对在其 Uses条款(clause)包含当前单元的任何编程单元有效
  • published - 与public节相同,但是编译器也生成这些类自动流所需的类型信息。通常published项目列表显示在Lazarus的对象察看器里;如果没有published列表,所有public域通常显示在对象察看器里。

方法

方法就像标准的过程或函数,但是可能有一些附加的指示directives)。

上面定义的一些方法以指示virtual标示;其它的以override指示标示。

  • virtual意味着方法的类型或实际的实例在编译时未知,但是在运行时依赖于实际调用方法的子程序来选择。可以把它认为是类定义里的占位者(place-holder )。[译者SunshineTech注:这与Java里的抽象方法是一样的,其在子类里具体实现,不同的子类有不同的实现方式。运行时根据子类来判断需要实际执行的方法实现。例如,假设有一个形状类,它定义了一个draw方法用来画出该形状,其子类如圆,正方形等具体来实现draw方法。]
  • override意味着在运行时本地指定的定义可以替换从祖先类继承的定义,特别是如果它是virtual。如果特别想使用祖先类里定义的方法,那么有时候必须使用inherited条款专门调用它。

没有virtual或override指示的方法是静态方法(Pascal的普通种类)。有virtual或override指示的方法动态方法。

方法的特殊实例是:

  • create - 类的构造器,关注于分配内存、收集所有需要的信息和配置/初始化各种属性。
  • destroy - 类的析构器,以一种顺序地和逻辑的方法从系统中删除类的所有部分,将所有资源返回给系统重用。

属性

属性就像常规Pascal纪录里的普通域,但是它们可以有read和/或write符号。

  • read符号是为属性返回正确类型的结果的一个域或函数。在上面的例子中,属性Color有一个读符号FColor,它是一个包含要使用值的本地变量。如果属性有read但没有write符号,那么它是只读的。
  • write符号是将属性值存储在指定位置的域或过程。在上面的例子里,Color有一个写符号SetColor,它是一个过程(在protected节定义),用来将颜色值写入一些指定的地方。如果一个属性有write但是无read符号,那么它是只写的。
  • default - 注意有可能为属性设置一个default值。例如,在创建时,Color在这里被指定了默认值clBlack,或黑色。它可能在后来由程序分配语句或在对象察看器里指定一个不同的值。


更多信息

这只是触及了主题的表面。需要更多细节,强烈建议读者阅读FreePascal手册,尤其是第5章(对象)和第6章(

--Kirkpatc 11:04, 20 July 2008 (CEST)

--SunshineTech翻译完成于2010年1月3日16:51(中国北京)


Console Mode Pascal