Delphi面向?qū)ο髮W(xué)習(xí)隨筆六:接口 作者:巴哈姆特 (轉(zhuǎn)載請注明出處并保持完整) 在對象化中,類的繼承是一個非常強大的機制;而更加強大的繼承機制應(yīng)該是來自從一個接口的繼承。 ??? 本篇我們將討論接口的特點。 ??? 首先,接口的定義方式與類相似。不同的是:類代表了一種實體,而接口代表了一批操作規(guī)范。還有,接口中所有的數(shù)據(jù)成員都是public訪問限制,也就是說,你不能為接口中的數(shù)據(jù)成員指定其為私有或其他的域成員。另外,接口中的方法只能有聲明而不能有實現(xiàn),因此它看上去更像是一個沒有構(gòu)造和析構(gòu)方法的純虛類。 ??? 我看的很多資料中,凡是在介紹接口的時候都會提到“多重繼承”,仿佛接口的存在只是為了彌補Object Pascal不支持多重繼承而設(shè)計的(至少給我的第一印象就是這樣),其實接口是非常強大的,也是對象化編程中不可或缺的一個重要組成部分。 ??? 接口之所以強大在于:接口只需要告訴用戶方法的名稱是什么,有什么參數(shù);而它并不需要理會方法是怎么實現(xiàn)的。例如電腦的構(gòu)造和工作方式對于一般用戶并不重要,因為一般用戶更關(guān)心的是如何去使用他。所以電腦的接口——鼠標(biāo)、鍵盤、顯示器等才是用戶最關(guān)心的地方。那么這就為我們實現(xiàn)對象化最核心的理念——“分離”提供了相當(dāng)大的便捷。 ??? 首先我們來看看接口的定義方式:下面是Delphi中System.pas里IInterface接口的聲名方式 type ? IInterface = interface ??? [''] ??? function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; ??? function _AddRef: Integer; stdcall; ??? function _Release: Integer; stdcall; ? end; 我們可以看到,和類的基本聲明差不多,只是由關(guān)鍵字class改成了interface。 ??? 大家也許會注意到在緊跟在聲明后的[''],這是什么呢?這是其實是接口的唯一標(biāo)識,也就是我們說的TGUID;當(dāng)把接口注冊給系統(tǒng)后,我們可以通過注冊表檢索到00000000-0000-0000-C000-000000000046這樣的鍵值。那么這就意味著,我們只需要知道一個TGUID的值就可以方便的訪問這個接口。 ??? 當(dāng)然,你可以在接口中定義其他的方法,但是Delphi中是不允許給接口添加變量成員的,因為接口是不允許有實現(xiàn)部分的。 ??? 同樣,接口也可以繼承、封裝以及方法的覆蓋。繼承接口同類繼承類似: type ? INewInterface = interface(IInterface) ??? // 定義一個新的接口INewInterface,并告訴編譯器它是繼承自IInterface接口 ??? .... ? end; 這里要注意一點,就是我們說過,接口的TGUID是每一個接口的唯一標(biāo)識,那么也就是說,TGUID是不能重復(fù)的。你不能簡單的從別處抄襲過來,那樣是錯誤的。如果你需要一個TGUID,你可以在Delphi的代碼編輯框中同時按下CTRL+SHIFT+G來獲得一個新的TGUID值,這個值是Delphi為你自動生成的。 ??? 接口的繼承大致就是這樣,和TObject是所有類的根類一樣,在Delphi中IInterface是所有接口的根類,那么類似于下面的繼承: type ? INewInterface = interface ??? ... ? end; 其實也是定義了一個繼承自IInterface的新接口INewInterface(和類名前加一個大寫字母T一樣,我們習(xí)慣于在接口名前加一個大寫字母I,當(dāng)然這只是一個命名約定) ??? 我們說了,接口只能有聲明,不能有實現(xiàn)。那么怎么讓接口為我們工作呢? ??? 其實,接口的實現(xiàn)是需要借助于類來完成的(當(dāng)然這看上去和C++中的多重繼承的寫法差不多)注意,既然接口是需要借助類來實現(xiàn)的,那么也就是說用來實現(xiàn)接口的類,必須實現(xiàn)接口中所有已定義的方法: type ? TNewInterfaceClass = class(TInterfacedObject, INewInterface) ??? // TInterfacedObject為類名,INewInterface為我們上面定義的接口名 ??? ... ? end; 一般,我們用來實現(xiàn)接口的基類不會選TObject而會選TInterfacedObject,理由是TInterfacedObject類已經(jīng)幫我們實現(xiàn)了IInterface接口中的方法,我們只需要實現(xiàn)我們自己接口中新的方法就可以了。 ??? 既然我們已經(jīng)通過類實現(xiàn)了接口中的方法,那么我們就可以使用這個接口來為我們服務(wù)了,實例化接口也非常簡單: var ? NewFace: INewInterface; begin ? NewFace:= TNewInterfaceClass.Create(); // 創(chuàng)建接口 ? NewFace.xxx; // 調(diào)用接口中的方法 ? NewFace:= nil; // 釋放 end; 有朋友可能會奇怪,接口的釋放為什么只是直接賦為nil?我們前面說過了:接口即沒有構(gòu)造方法,也沒有析構(gòu)方法。既然沒有析構(gòu)方法,那么就意味著我們不能用釋放類的方式來釋放接口。 ??? 那么直接把接口對象指空會造成內(nèi)存泄露嗎? ??? 答案是否定的,因為接口提供了一個引用記數(shù)的機制:當(dāng)某接口實例(也就是實現(xiàn)了這個接口的對象)被引用,比如被賦值給一個接口變量時,該接口實例的AddRef方法會被編譯器自動調(diào)用,引用計數(shù)將增加一。引用取消時編譯器則會調(diào)用_Release方法將引用計數(shù)減一。引用計數(shù)減到零時,表示已無其他接口變量引用此接口實例,此時編譯器會自動釋放它。 ??? 要注意的是,接口對象與類對象是不能混用的。當(dāng)然像我們上面的例子里,NewFace也只能調(diào)用INewInterface接口中所定義過的方法,而不能調(diào)用類中定義的不存在于接口中的方法。 ??? 當(dāng)然,一個類可以同時實現(xiàn)多個接口,多個接口彼此用逗號隔開。如: type ? TNewInterfaceClass = class(TInterfacedObject, IInterface1, IInterface2) ? ... ? end; 同樣,實現(xiàn)多個接口的類必須依次實現(xiàn)每個接口中定義的方法。 ??? 那么,這時出現(xiàn)了一個問題——就是當(dāng)兩個接口中有同名方法怎么辦?好辦,為他們?nèi)e名: type ? IInterface1 = interface(IInterface) ??? // 接口1 ??? fucntion Func(): Boolean; ? end; ? IInterface2 = interface(IInterface) ??? // 接口2 ??? function Func(): Boolean; ? end; ? TClasses = class(TInterfacedObject, IInterface1, IInterface2) ? public ??? function IInterface1.Func: Func1; ??? function IInterface2.Func: Func2; ????? { 為同名方法起別名 } ??? function Func1: Boolean; ??? function Func2: Boolean; ????? { 聲明方法 } ? end; Delphi中還可以使用imploements指示符用于委托另一個類或接口來實現(xiàn)接口的某個方法,有時這個方法又被稱為委托實現(xiàn),關(guān)于implements的用法如下: type ? TInterClass = class(TInterfacedObject, IInterface1) ??? ... ??? function GetClasses: TClasses; ??? property Face: TClasses read GetClasses implements IInterface1; ??? ... ? end; 上面的代碼中,implements指示字會要求編譯器在Face屬性中尋找實現(xiàn)IInterface1接口的方法,屬性的類型必須是一個類或一個接口。implements可以指定多個接口,彼此用逗號分隔。 ??? implements指示字的好處是: ??? 一、他允許以無沖突的方式進行接口聚合。(聚合是COM中的概念) ??? 二、他能夠延后占用實現(xiàn)接口所需要的資源,直到確實需要資源。 ??? 下面是關(guān)于委托的一個詳細例子(該例在DELPHI7 + WIN2000 SP4 中調(diào)試通過): ? INewInterface = interface(IInterface) ??? // 定義接口 ??? function SayHello: string; stdcall; ????? // 接口方法 ? end; ? TNewClass = class(TInterfacedObject, INewInterface) ? public ??? function SayHello: string; stdcall; ????? // 第一個類實現(xiàn)接口中的方法 ? end; ? TNewClass1 = class(TInterfacedObject, INewInterface) ? private ??? FNewClass: INewInterface; ? public ????? // 注意,在這個類中并沒有實現(xiàn)接口中的SyaHello方法 ??? constructor Create; ??? destructor Destroy; override; ??? property NewClass: INewInterface read FNewClass implements INewInterface; ????? // 接口對象委托 如果是類對象委托應(yīng)該是 ??? // property NewClass: TNewClass read FNewClass implements INewInterface; ? end; implementation function TNewClass.SayHello: string; begin ? Result:= ClassName; ? ShowMessage(Result); end; constructor TNewClass1.Create; begin ? inherited Create(); ? FNewClass:= TNewClass.Create; ??? // 在構(gòu)造方法中創(chuàng)建接口 end; destructor TNewClass1.Destroy; begin ? FNewClass:= nil; ??? // 在析構(gòu)方法中釋放接口 ? inherited Destroy(); end; 調(diào)用的例子: var ? NewInterface: INewInterface; begin ? NewInterface:= TNewClass1.Create; ? NewInterface.SayHello; ? NewInterface:= nil; end; 題外話:如果你還沒有接觸過COM/COM+的話,也許你會認為接口十分麻煩(PS: 當(dāng)年我剛學(xué)的時候真想一腳把發(fā)明接口機制的人踹死),但是接口經(jīng)過COM的封裝后,將變的非常的有意義,呵呵! |