发信人: mark7312(小马哥) 
整理人: soaringbird(2001-09-27 09:13:55), 站内信件
 | 
 
 
19.2.1.4. 测试未安装的部件
 
  
 
   在将新部件安装在Component Palette之前就能测试部件运行时的动作。这对于调试新部件特别有用,而且还能用同样的技术测试任意部件,无论该部件是否出现在Component Palette上。
 
   从本质上说,你通过模仿用户将部件放置在窗体中的Delphi的动作来测试一个未安装的部件。
 
   可按下列步骤来测试未安装的部件
 
   1. 在窗体单元的uses语句中加入部件所在单元的名字
 
 2. 在窗体中增加一个对象域来表示部件
 
   这是自己增加部件和Delphi增加部件的方法的主要不同点。
 
   你将对象域加在窗体类型声明底部的public部分。Delphi则会将对象域加在底部声明的上面。
 
   你不能将域加在Delphi管理的窗体类型的声明的上部。在这一部分声明的对象域将相应在存储在DFM文件中。增加不在窗体中存在的部件名将产生DFM文件无效的错误。
 
  3. 附上窗体的OnCreate事件处理过程
 
   4. 在窗体的OnCreate处理过程中构造该部件
 
   当调用部件的构造过程时,必须传递Owner参数(由Owner负责析构该部件)一般说来总是将Self作为Owner的传入参数。在OnCreate中,Self是指窗体。
 
   5. 给Component的Parent属性赋值
 
   设置Parent属性往往是构造部件后要做的第一件事时。Parent在形式上包含部件,一般来说Parent是窗体或者GoupBox、Panel。通常给Parent赋与Self,即窗体。在设置部件的其它属性之前最好先给Parent赋值。
 
   6. 按需要给部件的其它属性赋值
 
   假设你想测试名为TNewComponent类型的新部件,库单元名为NewTest。窗体库单元应该是这样的;
 
  
 
 unit Unitl;
 
  
 
 interface
 
  
 
 uses SysUtils, Windows, Messages, Classes, Grophics, Controls, Forms, Dialogs, 
 
 Newtest; 
 
 type
 
 Tforml = class(TForm)
 
 procedure FormCreate(Sender: TObject);
 
 private
 
 { private申 明 }
 
 public
 
 { public申 明 }
 
 NewComponent: TNewComponent;
 
 end; 
 
  
 
 var
 
 Forml: TForml; 
 
  
 
 implementation
 
  
 
 {$R *.DFM }
 
  
 
 procedure TForml.FormCreate ( Sender: TObject ) ;
 
 begin
 
 NewComponent := TNewComponent.Create ( Self );
 
 NewCompanent.Parent := Self;
 
 NewCompanent.Left := 12;
 
 end;
 
  
 
 end.
 
  
 
 19.2.1.5 编写部件的面向对象技术
 
  
 
   部件使用者在Delphi环境中开发,将遇到在包含数据和方法的对象。他们将在设计阶段和运行阶段操作对象,而编写部件将比他们需要更多的关于对象的知识,因此,你应当熟悉Delphi的面向对象的程序设计。
 
   1. 建立部件
 
   部件用户和部件编写者最基本的区别是用户处理对象的实例,而编写者创建新的对象类型。这个概念是面向对象程序设计的基础。例如,用户创建了一个包含两个按钮的窗体,一个标为OK,另一个标为Cancel,每个都是TButton的实例,通过给Text、default和Cancel等属性赋不同的值,给OnClick事件赋予不同的处理过程,用户产生了两个不同的实例。
 
 建立新部件一般有两个理由
 
   ● 改变类型的缺省情况,避免反复
 
   ● 为部件增加新的功能
 
  
 
   目的都是为了建立可重用对象。如果从将来重用的角度预先计划和设计,能节省一大堆将来的工作。
 
   在程序设计中,避免不必要的重复是很重要的。如果发现在代码中一遍又一遍重写相同的行,就应当考虑将代码放在子过程或函数中,或干脆建立一个函数库。
 
   设计部件也是这个道理,如果总是改变相同的属性或相同的方法调用,那应创建新部件。
 
   创建新部件的另一个原因是想给已有的部件增加新的功能。你可以从已有部件直接继承(如ListBox)或从抽象对象类型继承(如TComponent,TControl)。你虽然能为部件增加新功能,但不能将原有部件的属性移走,如果要这样做的话,就从该父对象的祖先对象继承。
 
   2. 控制部件的访向
 
   Object Pascal语言为对象的各部分提供了四个级别的访问控制。访问控制让你定义什么代码能访问对象的哪一部分。通过描述访问级别,定义了部件的接口。如果合理安排接口,将提高部件的可用性和重用性。
 
   除非特地描述,否则加在对象里的域、方法和属性的控制级别是published,这意味着任何代码可以访问整个对象。
 
   下表列出各保护级别:
 
  
 
  
 
 表19.2 对象定义中的保护级别
 
   ━━━━━━━━━━━━━━━━━━━
 
  保护级       用处
 
    ───────────────────
 
 private 隐藏实现细节
 
 protected     定义开发者接口
 
 public 定义运行时接口
 
 published 定义设计时接口
 
   ━━━━━━━━━━━━━━━━━━━
 
  
 
 所有的保护级都在单元级起作用。如果对象的某一部分在库单元中的一处可访向,则在该库单元任意处都可访向。
 
   ⑴ 隐藏实现细节
 
   如果对象的某部分被声明为private,将使其它库单元的代码无法访问该部分,但包含声明的库单元中的代码可以访问,就好象访问public一样,这是和C++不同的。
 
   对象类型的private部分对于隐藏详细实现是很重要的。既然对象的用户不能访问,private部分,你就能改变对象的实现而不影响用户代码。
 
   下面是一个演示防止用户访问private域的例子:
 
  
 
 unit HideInfo;
 
  
 
 interface
 
  
 
 uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, 
 
 Dialogs;
 
  
 
 type
 
 TSecretForm = class(TForm) { 声明新的窗体窗口 }
 
 procedure FormCreate(Sender: TObject);
 
 private { declare private part }
 
 FSecretCode: Integer; { 声明private域 }
 
 end;
 
  
 
 var
 
 SecretForm: TSecretForm;
 
  
 
 implementation
 
  
 
 procedure TSecretForm.FormCreate(Sender: TObject);
 
 begin
 
 FSecretCode := 42; 
 
 end;
 
  
 
 end. 
 
  
 
 unit TestHide; { 这是主窗体库单元 }
 
  
 
 interface
 
  
 
 uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms,
 
 Dialogs, HideInfo; { 使用带TSecretForm声明的库单元 }
 
 type
 
 TTestForm = class(TForm)
 
 procedure FormCreate(Sender: TObject);
 
 end;
 
  
 
 var
 
 TestForm: TTestForm;
 
  
 
 implementation
 
  
 
 procedure TTestForm.FormCreate(Sender: TObject);
 
 begin
 
 SecretForm.FSecretCode := 13; {编译过程将以"Field identifier expected"错误停止}
 
 end;
 
  
 
 end. 
 
  
 
   ⑵ 定义开发者接口
 
   将对象某部分声明为protected,可使在包含该部件声明的库单元之外的代码无法访问,就象private部分。protected部分的不同之处是,某对象继承该对象,则包含新对象的库单元可以访问protected部分,你能使用protected声明定义开发者的接口。也就是说。对象的用户不能访向protected部分,但开发者通过继承就可能做到,这意味着你能通过protected部分的可访问性使部件编写者改变对象工作方式,而又不使用户见到这些细节。
 
   ⑶ 定义运行时接口
 
   将对象的某一部分定义为public可使任何代码访问该部分。如果你没有对域方法或属性加以private、protected、public的访问控制描述。那么该部分就是published。
 
   因为对象的public部分可在运行时为任何代码访问,因此对象的public部分被称为运行接口。运行时接口对那些在设计时没有意义的项目,如依靠运行时信息的和只读的属性,是很有用的。那些设计用来供用户调用的方法也应放在运行时接口中。
 
   下例是一个显示两个定义在运行时接口的只读属性的例子:
 
  
 
 type 
 
 TSampleComponent = class(TComponent)
 
 private
 
 FTempCelsius: Integer; { 具体实现是private } 
 
 function GetTempFahrenheit: Integer; 
 
 public
 
 property TempCelsius: Integer read FTempCelsius; { 属性是public }
 
 property TempFahrenheit: Integer read GetTempFahrenheit; 
 
 end; 
 
  
 
 function GetTempFahrenheit: Integer; 
 
 begin 
 
 Result := FTempCelsius * 9 div 5 + 32; 
 
 end;
 
  
 
   既然用户在设计时不能改变public部分的属性的值,那么该类属性就不能出现在Object Inspector窗口中。
 
   ⑷ 定义设计时接口
 
   将对象的某部分声明为published,该部分也即为public且产生运行时类型信息。但只有published部分定义的属性可显示在Object Inspector窗口中。对象的published部分定义了对象的设计时接口。设计时接口包含了用户想在设计时定制的一切特征。
 
   下面是一个published属性的例子,因为它是published,因此可以出现在Object Inspector窗口:
 
  
 
 TSampleComponent = class(TComponent) 
 
 private 
 
 FTemperature: Integer; { 具体实现是 private }
 
 published
 
 property Temperature: Integer read FTemperature write FTemperature; { 可写的 } 
 
 end;
 
  
 
   3. 派送方法
 
   派送(Dispatch)这个概念是用来描述当调用方法时,你的应用程序怎样决定执行什么样的代码,当你编写调用对象的代码时,看上去与任何其它过程或函数调用没什么不同,但对象有三种不同的派送方法的方式。
 
   这三种派送方法的类型是:
 
   ● 静态的
 
   ● 虚拟的
 
   ● 动态的
 
  
 
   虚方法和动态方法的工作方式相同,但实现不同。两者都与静态方法相当不同。理解各种不同的派送方法对创建部件是很有用的。
 
  ⑴ 静态方法:
 
   如果没有特殊声明,所有的对象方法都是静态的.。静态方法的工作方式正如一般的过程和函数调用。在编译时,编译器决定方法地址,并与方法联接。
 
   静态方法的基本好处是派送相当快。因为由编译器决定方法的临时地址,并直接与方法相联。虚方法和动态方法则相反,用间接的方法在运行时查找方法的地址,这将花较长的时间。
 
   静态方法的另一个不同之处是当被另一类型继承时不做任何改变,这就是说如果你声明了一个包含静态方法的对象,然后从该对象继承新的对象,则该后代对象享有与祖先对象相同的方法地址,因此,不管实际对象是谁,静态方法都完成相同的工作。
 
   你不能覆盖静态方法,在后代对象中声明相同名称的静态方法都将取代祖先对象方法。
 
   在下列代码中,第一个部件声明了两静态方法,第二个部件,声明了相同名字的方法取代第一个部件的方法。
 
  
 
 type 
 
 TFirstComponent = class(TComponent) 
 
 procedure Move; 
 
 procedure Flash; 
 
 end;
 
  
 
 TSecondComponent = class(TFirstComponent) 
 
 procedure Move; { 尽管有相同的声明,但与继承的方法不同 } 
 
 function Flash(HowOften: Integer): Integer; { 同Move方法一样 } 
 
 end;
 
  
 
   ⑵ 虚方法
 
   调用虚方法与调用任何其它方法一样,但派送机制有所不同。虚方法支持在后代对象中重定义方法,但调用方法完全相同,虚方法的地址不是在编译时决定,而是在运行时才查找方法的地址。
 
   为声明一个新的方法,在方法声明后增加virtual指令。方法声明中的virtual指令在对象虚拟方法表(VMT)中创建一个入口,该虚拟方法表保存对象类所有虚有拟方法的地址。
 
   当你从已有对象获得新的对象,新对象得到自己的VMT,它包含所有的祖先对象的VMT入口,再增加在新对象中声明的虚拟方法。后代对象能覆盖任何继承的虚拟方法。
 
   覆盖一个方法是扩展它,而不是取代它。后代对象可以重定义和重实现在祖先对象中声明的任何方法。但无法覆盖一个静态方法。覆盖一个方法,要在方法声明的结尾增加override指令,在下列情况,使用override将产生编译错误:
 
   ● 祖先对象中不存在该方法
 
   ● 祖先对象中相同方法是静态的
 
   ● 声明与祖先对象的(如名字、参数)不匹配
 
  
 
   下列代码演示两个简单的部件。第一个部件声明了三个方法,每一个使用不同的派送方式,第二个部件继承第一个部件,取代了静态方法,覆盖了虚拟方法和动态方法。
 
  
 
 type 
 
 TFirstComponent = class(TCustomControl)
 
 procedure Move; { 静态方法 } 
 
 procedure Flash; virtual; { 虚 方 法 } 
 
 procedure Beep; dynamic; { 动态虚拟方法 } 
 
 end;
 
  
 
 TSecondComponent = class(TFirstComponent) 
 
 procedure Move; { 声明了新的方法 } 
 
 procedure Flash; override; { 覆盖继承的方法 }
 
 procedure Beep; override; { 覆盖继承的方法 } 
 
 end;
 
  
 
   ⑶ 动态方法
 
   动态方法是稍微不同于虚拟方法的派送机制。因为动态方法没有对象VMT的入口,它们减少了对象消耗的内存数量。派送动态方法比派送一般的虚拟方法慢。因此,如果方法调用很频繁,你最好将其定义为虚方法。
 
   定义动态方法时,在方法声明后面增加dynamic指令。
 
   与对象虚拟方法创建入口不同的是dynamic给方法赋了一数字,并存储相应代码的地址,动态方法列表只包含新加的和覆盖的方法入口,继承的动态方法的派送是通过查找每一个祖先的动态方法列表(按与继承“反转的顺序”),因此动态方法用于处理消息(包括Windows消息)。实际上,消息处理过程的派送方式与动态方法相同,只是定义方法不同
 
   ⑷ 对象与指针
 
   在Object Pascal中,对象实际上是指针。编译器自动地为程序创建对象指针,因此在大多数情况下,你不需要考虑对象是指针。但当你将对象作为参数传递时,这就很重要了。通常,传递对象是按值而非按引用,也就是说,将对象声明为过程的参数时,你不能用var参数,理由是对象已经是指针引用了。
 
 
  ---- 小马哥
  
 美丽的梦和美丽的诗一样,都是可遇而不可求的,
 常常在最没能料到的时候里出现
       | 
 
 
 |