Delphi物件模型(Part VI) (轉)

worldblog發表於2007-12-10
Delphi物件模型(Part VI) (轉)[@more@]

 

模型 (PART VI) :namespace prefix = o ns = "urn:schemas--com::office" />

 

Delphi對於物件導向的支援豐富而且強大。除了傳統的類和物件,Delphi還提供了介面,異常處理,多執行緒程式設計等特性。這一章節深入講解了Delphi的物件模型。讀者應當對標準的Pascal比較熟悉,並且對有關物件導向程式設計的基本法則有一定了解。

(本文的英文原文將Delphi與 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 := .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 NumSs: 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方法。也就是說,永遠不要嘗試使用指向無效指標的變數,使用介面而不使用引用計數強制你必須這麼做。

 

/develop/read_article.?id=10403">PartI

PartII

PartIII

PartIV

PartV

PartVI

更多文章


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990741/,如需轉載,請註明出處,否則將追究法律責任。

相關文章