Delphi物件模型(Part VI) (轉)
模型 (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
PartVI
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990741/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Delphi物件模型(Part V) (轉)物件模型
- Delphi物件模型(Part IV) (轉)物件模型
- Delphi物件模型(Part II) (轉)物件模型
- Delphi物件模型(Part III) (轉)物件模型
- Delphi中的類和物件 (轉)物件
- delphi的物件導向之路1 (轉)物件
- oracle Administrator's guide part V & VIOracleGUIIDE
- 擴充套件Delphi的執行緒同步物件(1) (轉)套件執行緒物件
- Delphi 中物件導向程式設計之我見 (轉)物件程式設計
- oracle database backup and recovery user's guide part V & VIOracleDatabaseGUIIDE
- ASP.NET 頁面物件模型 (轉)ASP.NET物件模型
- 在DELPHI程式中使用ADO物件存取ODBC資料庫 (轉)物件資料庫
- vi命令詳解(轉)
- vi編輯命令(轉)
- Delphi與Excel (轉)Excel
- 【VMware VCF】VMware Cloud Foundation Part 06:部署 VI 工作負載域。Cloud負載
- 網路IO模型-非同步選擇模型(Delphi版)模型非同步
- 在Delphi與C++之間實現函式與物件共享 (轉)C++函式物件
- vi命令速查表(轉)
- vi學習體系(轉)
- 典型物件池模型的“物件過早歸還”現象分析 (轉)物件模型
- django入門-模型-part2Django模型
- Linux下vi的用法(轉)Linux
- vi常用命令(轉載)
- Vi 簡介 linux操作 (轉)Linux
- [譯] part 26: golang 的物件導向Golang物件
- C++物件模型之二 構造語句 (轉)C++物件模型
- C++物件模型之六 執行期筆記 (轉)C++物件模型筆記
- DOM 模型(文件物件模型)重點模型物件
- CentOS7 vi編輯命令【轉】CentOS
- VI高階命令集錦(轉)
- vi 命令常見問題解答(轉)
- Linux循序漸進(22):vi(轉)Linux
- 熟練掌握vi的控制操作(轉)
- C++ 物件模型C++物件模型
- C++物件模型C++物件模型
- 新增模型物件操作模型物件
- XML簡明教程(4)什麼是XML物件模型(轉)XML物件模型