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的封裝後,將變的非常的有意義,呵呵!
完全重載!