分享
 
 
 

Delphi对象模型(Part VI)

王朝delphi·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Delphi对象模型 (PART VI)

Delphi对于面向对象编程的支持丰富而且强大。除了传统的类和对象,Delphi还提供了接口,异常处理,多线程编程等特性。这一章节深入讲解了Delphi的对象模型。读者应当对标准的Pascal比较熟悉,并且对有关面向对象编程的基本法则有一定了解。

(本文的英文原文将Delphi与Object Pascal统一表述为Delphi,可能有概念不清之嫌疑。但在大多数情况下,相信读者能够根据上下文来判定文中所述之Delphi的具体含义——译者注。)

Interfaces接口

接口定义了包含一组抽象方法的类型。一个类,即使是自一个简单的基类继承而来也可以实现任意多的借口。接口与抽象类有些相似(即没有任何字段并且所有方法都是抽象方法的类),并且Delphi提供了附加的功能。Delphi的接口有时很象COM(组件对象模型)借口,但是使用Delphi的接口并不需要你了解有关COM的内容,同时你还可以将接口用作其他许多用途。

你可以声明一个新的接口——它继承于一个已经存在的接口。接口的声明包含了方法和属性的声明,但是没有字段。正如所有的类都继承于TObject一样,所有的接口类继承自IUnknown。接口IUnknown定义了三个方法:_AddRef,_Release,以及QueryInterface。如果你对COM熟悉的话,对此三个方法便不会陌生。前两个方法用于管理实现此接口的对象的生命周期引用计数。第三个方法用于存取对象可能实现的其他接口。

当你想要声明一个实现了一个或者多个接口的类时,你必须实现接口中所声明的所有方法。新的类可以直接实现接口的方法,也可以将此实现委托给一个属性——其值为一个接口。实现_AddRef,_Release以及QueryInterface方法最简单的方法就是继承TInterfacedObject及其派生类的方法,当然你也可以继承自其他类如果你想自己定以方法的实现的话。

新类在实现接口的方法时必须使用于接口方法一致的方法名,参数以及调用约定。Delphi自动将类的方法与接口的相应方法配对。假如要使用不同的方法名,你可以使用不同的方法名来重定向接口的方法。用作重定向的方法必须具有于接口的方法一致的参数和调用约定。这一特性非常重要,当一个类需要实现多个接口,而其中有重复的方法名时尤其如此。请在第五章查找关键字Class,以获得有关重定向方法的更多内容。

类可以使用implements指示符将接口的实现委托给一个属性。该属性的值必须得是该类将要实现的接口类型。当对象被映射到该接口上时,Delphi自动获取该属性的值,并且返回该接口。参考第五章中关于implements指示符的内容。

对于每个非委托方式实现的接口,编译器为其创建一个隐含的字段用于存放指向该接口的VMT。接口的字段正好位于对象隐含的VMT字段之后。正如对象引用其实是指向对象的隐含的VMT字段的指针,接口的引用也是指向隐含的VMT字段的一个指针。当对象被创建时Delphi自动初始化隐含字段。参考第三章有关编译器如何使用RTTI来追踪VMT和隐含字段的内容。

Reference counting引用计数

编译器触发对_AddRef和_Release的调用以管理接口对象的生命周期。要使用Delphid的自动的引用计数,声明一个接口类型的变量即可。当你将一个接口引用赋值给一个接口变量时,Delphi自动调用_AddRef。当改变量离开作用域时,Delphi自动调用_Release。

_AddRef和_Release的行为完全取决于你。如果你从TInterfacedObject继承,则这些方法完成引用计数的功能。_AddRef方法用于增加引用计数,_Release用于将引用计数减一。当引用计数为0时,_Release方法将释放对象。如果你从其他类继承而来,则你可以定义自己的方法。但是,你应当正确的实现QueryInterface方法,因为Delphi正是基于此来实现As操作。

Typecasting类型转换

Delphi调用QueryInterface来对接口实现部分as操作的功能。你可以使用as操作符将一个接口转换为另外一个接口。Delphi调用QueryInterface以获得一个新的接口引用。如果QueryInterface返回一个错误,则as操作将触发一个运行期错误。(在SysUtils单元中该运行其错误被映射到EIntfCastError异常类中。)

你可以用自己的方式来实现QueryInterface方法,虽然可能你更倾向于与TInterfacedObject的实现接近的那种。例子 2-13 显示的是一个类实现了普通的QueryInterface方法,但是对于_AddRef和_Release方法的实现确大不相同。稍后你将看到这样做有什么用处。

例 2-13:无需引用计数的接口类

type

TNoRefCount = class(TObject, IUnknown)

protected

function QueryInterface(const IID:TGUID; out Obj):HResult; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

end;

function TNoRefCount.QueryInterface(const IID:TGUID; out Obj): HResult;

begin

if GetInterface(IID, Obj) then

Result := 0

else

Result := Windows.E_NoInterface;

end;

function TNoRefCount._AddRef: Integer;

begin

Result := -1

end;

function TNoRefCount._Release: Integer;

begin

Result := -1

end;

Interfaces and object-oriented programming接口和面向对象编程

接口最重要的作用就是将类型继承(type inheritance)与类继承(class inheritance)分开。类继承是代码重用的一项有效的工具。派生的类轻松的继承了基类的字段,方法以及属性,并且不需要重新实现公用的方法。在一个强类型的语言中,比如Delphi中,编译器将一个类看作是一种类型,因此类继承与类型继承的概念似乎有些重叠了。但是尽可能地,我们对于类型(type)和类(Class)还是应当严格区分。

许多有关面向对象编程的书籍都将继承关系描述为“是”的关系,比如,一个TSavingsAccount“是”TAccount。你可以体会到相同的含义,当你使用Delphi的Is操作符,来测试一个Account变量是否是TSavingsAccount。

上文例子中的简单的“是”关系已经不能适应要求。正方形属于矩形的一种,但这并不意味着你愿意将TSquare继承自TRectangle。矩形属于多边形的一种,但你可能不希望TRectangle继承自TPolygon。类继承强制派生的类保存基类中声明的所有字段,但这种情况下,派生类并不需要这些信息。一个TSquare对象只需保存它所有边的一个单一长度。然而,一个TRectangle对象却必须保存两个长度。一个TPolygon对象则需要保存许多条边和顶点位置。

解决的方案就是将其从类继承(类C继承了B的字段和方法,而B则继承了A的字段和方法)分离为类型继承(正方形是矩形,矩形又是多边形)。使用接口实现类型继承,则你可以让类继承做它擅长的:字段和方法的继承。

换句话说就是,ISquare继承自IRectangle,而后者又继承自IPolygon。接口遵从了“是”的关系。完全的与接口分离,类TSquare实现了接口ISquare和IRectangle和IPolygon。TRectangle实现了IRectangle和IPolygon。

提示:

COM编程的一个约定是将接口的名称命名为I打头的。Delphi的所有接口都遵循了这个约定。注意这只是一个有用的约定,并不是语言的强制要求。

从实现上而言,你可以声明符加的类以达到代码重用的目的。比如,使用TBaseShape实现对所有形状的公用字段和方法。TRectangle继承自TBaseShape然后实现跟根据矩形的特点实现相应方法。多边形依然继承自TBaseShape,并且根据多边形的特点实现相应的方法。

一个画图程序可以操作IPolygon接口来使用各种形状。例子 2-14显示的是基于这种设想的简单的类和接口。注意到每个接口都同时声明了GUID(全局唯一标识符)。使用QueryInterface时GUID是必须的。如果要使用接口的GUID,你可以直接使用接口的名称。Delphi会自动将接口的名称转换为对应的GUID。

例 2-14:分离类型和类继承

type

IShape = interface

['{50F6D851-F4EB-11D2-88AC-00104BCAC44B}']

procedure Draw(Canvas: TCanvas);

function GetPosition: TPoint;

procedure SetPosition(Value: TPoint);

property Position: TPoint read GetPosition write SetPosition;

end;

IPolygon = interface(IShape)

['{50F6D852-F4EB-11D2-88AC-00104BCAC44B}']

function NumVertices: Integer;

function NumSides: Integer;

function SideLength(Index: Integer): Integer;

function Vertex(Index: Integer): TPoint;

end;

IRectangle = interface(IPolygon)

['{50F6D853-F4EB-11D2-88AC-00104BCAC44B}']

end;

ISquare = interface(IRectangle)

['{50F6D854-F4EB-11D2-88AC-00104BCAC44B}']

function Side: Integer;

end;

TBaseShape = class(TNoRefCount, IShape)

private

fPosition: TPoint;

function GetPosition: TPoint;

procedure SetPosition(Value: TPoint);

public

constructor Create; virtual;

procedure Draw(Canvas: TCanvas); virtual; abstract;

property Position: TPoint read fPosition write SetPosition;

end;

TPolygon = class(TBaseShape, IPolygon)

private

fVertices: array of TPoint;

public

procedure Draw(Canvas: TCanvas); override;

function NumVertices: Integer;

function NumSides: Integer;

function SideLength(Index: Integer): Integer;

function Vertex(Index: Integer): TPoint;

end;

TRectangle = class(TBaseShape, IPolygon, IRectangle)

private

fRect: TRect;

public

procedure Draw(Canvas: TCanvas); override;

function NumVertices: Integer;

function NumSides: Integer;

function SideLength(Index: Integer): Integer;

function Vertex(Index: Integer): TPoint;

end;

TSquare = class(TBaseShape, IPolygon, IRectangle, ISquare)

private

fSide: Integer;

public

procedure Draw(Canvas: TCanvas); override;

function Side: Integer;

function NumVertices: Integer;

function NumSides: Integer;

function SideLength(Index: Integer): Integer;

function Vertex(Index: Integer): TPoint;

end;

派生类继承了祖先类实现的接口。TRectangle继承自TBaseShape,则TBaseShape实现了IShape接口也就是TRectangle实现了IShape接口。而接口的继承与此有些不同。接口的继承仅仅为了类型上的便利,也就是说你不必重新再去输入许多方法的声明。当一个类实现一个接口时,并不意味着该类自动的实现了祖先的接口。事实上,该类只实现了出现在该类的声明部分的这些接口(以及在祖先类的声明部分出现的接口)。因此,即使IRectangle继承自IPolygon,TRectangle类还是得将IRectangle和IPolygon显式的罗列出来。

要实现类型体系,你不应当使用引用计数。相反,你需要实现显式的内存管理,如同处理普通的Delphi对象一样。在这种情况下,实现_AddRef和_Release 方法的最好办法就是连根拔除,就象我们在例 2-13里见到的TNoRefCount类那样。还有需要注意的是,不要有任何变量指向失效的引用。一个已经被释放的对象引用可能导致问题,因为Delphi将会自动调用_Release方法。也就是说,永远不要尝试使用指向无效指针的变量,使用接口而不使用引用计数强制你必须这么做。

PartI

PartII

PartIII

PartIV

PartV

PartVI

更多文章

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有