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