{在理解了OOP之后,我们对于组件所要必备的另一个知识点:接口 进行阐述,也许作为刚刚接触接口的您来说,这有些枯燥,但是必须给您说明的是,此处所说的接口将会百分百的引用到以后的组件技术或是组件对象中,因为它们最终就是对接口的实现、封装!引用上篇文章}
什么是接口? 接口有什么作用?如何用接口?
一系列的问题都会缠绕着你,如果你不想做组件、分布式、本地的应用程序调用的话,就不用看了;
COM1/COM2等硬件接口,我们都不陌生;但是如果要将软件中的接口和它们一样吗?又如何用接口呢?而且用好的话,并不一定很容易;让我们继续吧;
我们所谓的接口其实就是一些过程、函数、属性集;记住,接口不可以有字段的,如果你有这个想法的话,那么从现在开始就要认识是错误的,对接口的访问就是对它提供的方法、事件、属性的访问,而且,接口所提供的方法都是公开的,是全部的公开的,所以就不必要用Public了;
在组件中,接口就是一切,一个组件就是一个接口集,用户只用通过接口才能组件进行打交道;
最通用的接口
IunKnown: InterFace;//默认接口
首先我们来看一个简单的例程,然后进行详细的介绍;
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,ComObj,ActiveX,StdVCL;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
ILC = Interface(IUnknown)
['{4FFE6DDB-80B9-4E2D-A05F-5F3B35311ED7}']
//GUID,它是用来唯一标识一个接口的标识符,可以通过Ctrl + Shift + G产生一组GUID,而且你可以认为你所产生的GUID是全世界唯一的。永远不要担心GUID会被用完。
procedure SetValue(NewValue:String);
function GetValue:String;
end;
TLC = Class(TInterfacedObject, ILC)
public
Value:String;
procedure SetValue(NewValue:String);
function GetValue:String;
destructor Destroy;override;
end;
var
Form1: TForm1;
IMyLC : ILC;
implementation
{$R *.dfm}
{ TLC }
destructor TLC.Destroy;
begin
Application.MessageBox('资源已经被完全释放','操作提示',MB_OK + MB_ICONINFORMATION);
inherited;
end;
function TLC.GetValue: String;
begin
Result := Value;
ShowMessage(Result);
end;
procedure TLC.SetValue(NewValue: String);
begin
Value := NewValue;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
IMyLC.SetValue('第一个COM例程');
IMyLC.GetValue;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IMyLC := TLC.Create;
end;
end.
Interfaces接口
接口定义了包含一组抽象方法的类型。为什么说是包含了一组抽象的方法类型呢?原因是接口的继承可以完全进行类如类中的Overload(当然,没有这样的语法,但是效果是完全一样的),这也正是接口的特殊所在,同时,接口里的方法是完全公用的,就如Public,但没有必要加这个关键字。一个类,即使是自一个简单的基类继承而来也可以实现任意多的接口。接口与抽象类有些相似(即没有任何字段并且所有方法都是抽象方法的类),并且Delphi提供了附加的功能。Delphi的接口有时很象COM(组件对象模型)接口,然而,可以利用delphi为我们提供的Interface实现组件对象模型的接口,但是使用Delphi的接口并不需要你了解有关COM的内容,同时你还可以将接口用作其他许多用途。
声明一个新的接口——它继承于一个已经存在的接口。默认的继承于IunKown接口,接口的声明包含了方法和属性的声明,但是没有字段。正如所有的类都继承于TObject一样,所有的接口类继承自IUnknown。接口IUnknown定义了三个方法:_AddRef,_Release,以及QueryInterface。如果你对COM熟悉的话,对此三个方法便不会陌生。前两个方法用于管理实现此接口的对象的生命周期引用计数。第三个方法用于存取对象可能实现的其他接口。至于引用计数则是一个比较活的引用,在OOP里,很多地方都引用了引用计数技术。你将会看到,通过引用计数功能,来对组件进行一些违背组件初衷的操作,比如提前释放组件、推迟释放组件,甚至不释放组件,当然,我不推荐这种作法,除非你这样做有自己充分的理由,在后边的例程中,你将会看到这样的做法。
声明了接口并不等于就可以应用这个接口,必须要有一个协调对象类(CoClass)来实现这个类,如果你对Type Library比较熟悉的话,应该对这个实现类很清楚,这是后话,您将会在ocx实例中看到它的用法。在此强调一点:没有实现的接口是没有任何意义的,之所以这样说是因为某一个接口最终目的就是为用户服务,被用户所用,但是只有对这个接口进行了实现才可以对其应用(前边我提起过,接口类如一个抽象类的原因也是如此的,而且强调接口没有字段是因为它不具备存储的能力!);
提醒各位一点,delphi不支持多继承,但是可以利用接口来实现多继承,还有另外一种可以通过设计模式来实现多继承(不属于我们讨论的范畴),当你想要声明一个实现了一个或者多个接口的类时,你必须实现接口中所声明的所有方法。新的类可以直接实现接口的方法,也可以将此实现委托给一个属性——其值为一个接口。实现_AddRef,_Release以及QueryInterface方法最简单的方法就是继承TInterfacedObject及其派生类的方法,当然你也可以继承自其他类如果你想自己定以方法的实现的话。如:
TvClass = Class(TinterfacedObject,IvInterface);
理解:TvClass作为IvInterface接口的一个实现类,它是继承于TinterfacedObject而实现于IvInterface的,同时,它了可以实现多个接口。
新类在实现接口的方法时必须使用于接口方法一致的方法名,参数以及调用约定。Delphi自动将类的方法与接口的相应方法配对。如例程:
var
MyObject : Tobject;
MyNumber : IformattedNumber;
Begin
MyObject := TformattedInteger.Create(12);
…
if MyObject.GetInterface(IformattedNumber,MyNumber) then
ShowMessage(MyNumber.FormattedString);
End;
{说明:此处的实例摘自我的一个例程,详细可参考例程,IformattedNumber是一个接口,TformattedNumber是它的CoClass}
假如要使用不同的方法名,你可以使用不同的方法名来重定向接口的方法。用作重定向的方法必须具有于接口的方法一致的参数和调用约定。这一特性非常重要,当一个类需要实现多个接口,而其中有重复的方法名时尤其如此。在这种情况下就需要将某一个接口的方法利用别名或是利用Implements指示符将接口的实现委托给一个属性。该属性的值必须得是该类将要实现的接口类型。当对象被映射到该接口上时,Delphi自动获取该属性的值,并且返回该接口。在此将不对这些细节问题进行阐述。
Reference counting引用计数
对象有一个创建、释放的过程,接口也同样有,组件更是如此,接口也有生命周期,接口的生命周期是随着一个计数变量(FCount)而决定的。而何时释放一个组件是属于引用技术范畴,(期望以后的文章我会对此进行阐述)某一个组件的释放有四种方式,它可以随着其接口、自身、以及组件对象的决定而在不同的时间进行释放,以及强制释放 IinterFace := Nil在此,你现在只要明白了接口、组件的释放和引用计数变量有很大的关系。
编译器触发对_AddRef和_Release的调用以管理接口对象的生命周期。要使用Delphi的自动的引用计数,声明一个接口类型的变量即可。当你将一个接口引用赋值给一个接口变量时,Delphi自动调用_AddRef。当改变量离开作用域时,Delphi自动调用_Release。
_AddRef和_Release的行为完全取决于你。如果你从TInterfacedObject继承,则这些方法完成引用计数的功能。_AddRef方法用于增加引用计数,_Release用于将引用计数减一。当引用计数为0时,_Release方法将释放对象。如果你从其他类继承而来,则你可以定义自己的方法。但是,你应当正确的实现QueryInterface方法,因为Delphi正是基于此来实现As操作。对于引用计数技术,我将会在另外的文章中加以阐述,并且就不同的情况下,_AddRef and _Release操作以及接口转化等做详细的说明,如果你有兴趣请关注我近期内写的一些文章。在下边将实现一些简单的转代以供参考。
{
说明:TinterfacedObject实现了IunKown的_AddRef 和 Release以及QueryInterface,所以我们所声明的CoClass只要继承自它就可以了,否则可能需要你自己写这三个方法的实现方法,然而,这并非是决对的。只是TinterFacedObject提出的比较早,所以,很多资料都是以它作为基类。在下边的实例中,你将可以看到如何手动的实现这三个方法,而且破坏引用计数的规则。
}
Delphi调用QueryInterface来对接口实现部分as操作的功能。你可以使用as操作符将一个接口转换为另外一个接口。Delphi调用QueryInterface以获得一个新的接口引用。如果QueryInterface返回一个错误,则as操作将触发一个运行期错误。(在SysUtils单元中该运行其错误被映射到EIntfCastError异常类中。)
你可以用自己的方式来实现QueryInterface方法,虽然可能你更倾向于与TInterfacedObject的实现接近的那种。如下显示的是一个类实现了普通的QueryInterface方法,但是对于_AddRef和_Release方法的实现确大不相同。稍后你将看到这样做有什么用处
无需引用计数的接口类
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;
在此我之所以要花这么多的篇幅来说明接口就是应为接口是组件中的一切,脱离了接口的组件将不成组件,然而这里所介绍的接口却又是少的可怜,我会在以后的文章中进行详细的说明。各位现在对接口是否有一个初步的了解了呢?如果你感觉有很多疑问而且又迫切的想知道答案,可以联系我,或是到www.nxit.net上看我以前的有关于接口的文章以及收录在超级猛料中的丁点介绍。
之后,我们将进行一些组件的制作,请关注下一篇文章.
转贴请注明 作者:dprogram