2012-07-01 35 views
4

幾天前我發佈了a question,答案告訴我要創建自己的類。如何開始使用Delphi創建自己的類?

我是OOP之前的老派程序員,我的編程結構合理,高效和組織良好,但缺少使用Delphi和第三方對象的自定義OOP。

我曾經看過Delphi的面向對象類在開始使用Delphi 2時如何工作,但它們似乎與我的編程背景無關。我明白他們是怎麼樣,對於設計組件和用戶界面上的視覺控件的開發人員來說是非常優秀的。但是我從來沒有發現需要在程序本身的編碼中使用它們。

所以現在我再看看,15年後,在Delphi的課程和OOPing。如果我取,例如,一個結構,我有,例如:

type 
    TPeopleIncluded = record 
    IndiPtr: pointer; 
    Relationship: string; 
    end; 
var 
    PeopleIncluded: TList<TPeopleIncluded>; 

然後一個面向對象的倡導者可能會告訴我,使這一類。從邏輯上講,我認爲這將是一個從通用TList繼承的類。我想這將這樣進行:

TPeopleIncluded<T: class> = class(TList<T>) 

但是,這是我卡住,並沒有對如何做加時賽剩下的良好指示。

當我看到一些類,德爾福作爲Generics.Collections單元一個例子,我看到:

TObjectList<T: class> = class(TList<T>) 
private 
    FOwnsObjects: Boolean; 
protected 
    procedure Notify(const Value: T; Action: TCollectionNotification); override; 
public 
    constructor Create(AOwnsObjects: Boolean = True); overload; 
    constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload; 
    constructor Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload; 
    property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects; 
end; 

,然後他們的構造函數的定義和程序是:

{ TObjectList<T> } 

constructor TObjectList<T>.Create(AOwnsObjects: Boolean); 
begin 
    inherited; 
    FOwnsObjects := AOwnsObjects; 
end; 

constructor TObjectList<T>.Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean); 
begin 
    inherited Create(AComparer); 
    FOwnsObjects := AOwnsObjects; 
end; 

constructor TObjectList<T>.Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean); 
begin 
    inherited Create(Collection); 
    FOwnsObjects := AOwnsObjects; 
end; 

procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification); 
begin 
    inherited; 
    if OwnsObjects and (Action = cnRemoved) then 
    Value.Free; 
end; 

讓我告訴你,這個「簡單」的類定義對於那些在Delphi使用OOP多年的人來說可能是顯而易見的,但對我而言,它只爲我提供了數百個未解答的問題,關於我該如何使用以及如何使用它。

對我而言,這似乎並不是一門科學。它似乎是一種如何最好地將信息結構化爲對象的藝術。

所以,這個問題,我希望它不會關閉,因爲我真的需要幫助,是在哪裏或如何獲得使用Delphi創建類的最佳指令 - 以及如何做到正確的德爾福辦法。

+0

你使用記錄的代碼對我來說看起來很好。 –

+0

如果你還不熟悉OOP,我不會直接使用泛型。泛型和類元類是一些需要「調整」的概念。不是說你應該遠離泛型。一點也不。但首先給自己一個「普通」OOP的機會。也許在一些試驗項目中。當你創建了自己的TObject後代和TObjectList(普通而不是通用的)後代,並編寫了代碼以類型安全的方式獲取列表中的實例幾次後,您將開始感受泛型類中有什麼。 –

+0

@David:這只是從Generics.Collections複製的代碼,所以它看起來更好。但是我在試圖將它應用到從TList 繼承的我自己的數據結構的需求方面有很多問題。 – lkessler

回答

8

對我而言,這似乎並不是一門科學。它似乎是一個藝術 如何最好地將您的信息結構化爲對象。

嗯......是的。真的沒有太多的正式要求。這實際上只是一組工具,可幫助您組織自己的想法,並在此過程中消除大量重複。

然後,一個面向對象的倡導者可能會告訴我要把它做成一個類。從邏輯上講,我認爲這將是一個從通用TList繼承的類。

事實上,通用集裝箱的整點是,你必須做出新的容器類每種類型的對象。相反,你會創建一個新的內容類,然後創建一個TList<TWhatever>

將類實例想象爲指向記錄的指針。

現在:當你可以使用指向記錄的指針時,爲什麼要使用類?一對夫婦的原因:

  • 封裝:您可以隱藏實現的某些方面與private關鍵字,以便其他開發者(包括你的未來的自己)知道不依賴於實現細節可能會改變,或者只是AREN瞭解這個概念並不重要。
  • 多態性:通過爲每條記錄提供一組函數指針,可以避免大量特殊的調度邏輯。然後,您不必爲每種類型的對象執行不同的操作,而是通過遍歷列表並向每個對象發送相同的消息,然後遵循函數指針來決定要執行的操作。
  • 繼承:當您開始製作記錄時,通過指向函數和過程的指針,您會發現您經常遇到需要一個新的函數 - 調度記錄,這與您已有的非常相似,除非需要更改或兩個程序。子類化只是實現這一目的的一種便捷方式。

因此,在你的其他職位,你指出你的整體方案是這樣的:

procedure PrintIndiEntry(JumpID: string); 
    var PeopleIncluded : TList<...>; 
begin  
    PeopleIncluded := result_of_some_loop; 
    DoSomeProcess(PeopleIncluded); 
end; 

這不是很清楚,我什麼IndiJumpID平均,所以我要假裝你的公司做跳傘的婚禮,那Indi的意思是「個人」,而JumpID是數據庫中的主要關鍵字,表明所有這些人都參加婚禮派對並且計劃跳出同一架飛機的飛行......而且這對於知道他們的Relationship對幸福的夫婦,讓你可以給他們正確的顏色降落傘。

很顯然,這並不完全符合你的域名,但由於你在這裏提出一個普遍的問題,所以細節並不重要。

另一篇文章中的人試圖告訴你(我的猜測)不是用一個類替換你的列表,而是用一個替換你的列表。

換句話說,不是將JumpID傳遞給過程並使用它從數據庫中獲取人員列表,而是創建一個Jump類。

如果你的JumpID實際上表示跳轉,如goto,那麼你可能實際上有一堆類都是同一類的子類,並以不同的方式覆蓋相同的方法。

事實上,讓我們假設你做一些當事人不在婚禮,而在這種情況下,你不需要關係,但只有少部分人一個簡單的列表:

type TPassenger = record 
    FirstName, LastName: string; 
end; 

type TJump = class 
    private 
    JumpID : string; 
    manifest : TList<TPassenger>; 
    public 
    constructor Init(JumpID: string); 
    function GetManifest() : TList<TPassenger>; 
    procedure PrintManifest(); virtual; 
end; 

所以現在PrintManifest()可以完成PrintIndyEntry()的工作,但不是在線計算列表,而是調用Self.GetManifest()

現在也許你的數據庫變化不大,而你的TJump實例總是很短暫,所以你決定在構造函數中填充。在這種情況下,GetManifest()只是返回該列表。

或者您的數據庫可能會頻繁更改,或者TJump會持續很長時間,以致數據庫可能會在其下更改。在這種情況下,每次調用時,GetManifest()都會重建列表...或者您可能會添加另一個private值,指示您上次查詢的時間,並且只在信息過期後才更新。

問題是PrintManifest不需要關心GetManifest是如何工作的,因爲你已經隱藏了這些信息。

當然,在Delphi中,您可以使用unit做同樣的事情,在implementation部分隱藏緩存的乘客列表清單。

但clasess帶來多一點的表,當談到時間來實現婚禮黨特有的功能:

type TWeddingGuest = record 
    public 
    passenger : TPassenger; 
    Relationship : string; 
end; 

type TWeddingJump = class (TJump) 
    private 
    procedure GetWeddingManifest() : TList<TWeddingGuest>; 
    procedure PrintManifest(); override; 
end; 

所以在這裏,在TWeddingJump繼承InitTJumpGetManifest,但它還增加了一個GetWeddingManifest();,它將用一些自定義實現覆蓋PrintManifest()的行爲。 (你知道它在做什麼,因爲override標記在這裏,這相當於virtual標記在TJump這一點。

但現在,假設PrintManifest實際上是一個相當複雜的過程,並且你不想重複所有的代碼當你想要做的就是添加在頭一列,並在體內列出關係領域的另一列你能做到這一點,像這樣:

type TJump = class 
    // ... same as earlier, but add: 
    procedure PrintManfestHeader(); virtual; 
    procedure PrintManfiestRow(passenger:TPassenger); virtual; 
end; 
type TWeddingJump = class (TJump) 
    // ... same as earlier, but: 
    // * remove the PrintManifest override 
    // * add: 
    procedure PrintManfestHeader(); override; 
    procedure PrintManfiestRow(passenger:TPassenger); override; 

end; 

現在,你想這樣做:

procedure TJump.PrintManifest() 
    var passenger: TPassenger; 
begin; 
    // ... 
    Self.PrintManifestHeader(); 
    for guest in Self.GetManifest() do begin 
     Self.PrintManifestRow(); 
    end; 
    // ... 
end; 

但是,您還不能,因爲GetManifest()返回TList<TPassenger>;TWeddingJump,您需要它返回TList<TWeddingGuest>

那麼,你如何處理?

在你的原代碼,你有這樣的:

IndiPtr: pointer 

指向什麼?我的猜測是,就像這個例子一樣,你有不同類型的個體,你需要它們做不同的事情,所以你只需要使用一個通用指針,並讓它指向不同類型的記錄,並且希望你把它投射到以後是正確的。但班給你一些更好的方法來解決這個問題:

  • 你可以做TPassenger類,並添加一個GetRelationship()方法。這將消除對TWeddingGuest的需求,但這意味着即使你不是在討論婚禮,方法總是在附近。
  • 您可以在TWeddingGuest課程中添加GetRelationship(guest:TPassenger),只需在TWeddingGuest.PrintManifestRow()以內調用即可。

但假設您必須查詢數據庫才能填充該信息。使用上述兩種方法,您將爲每位乘客發出一個新查詢,這可能會導致數據庫停滯不前。你真的想一次性獲取所有東西,在GetManifest()

所以,相反,你又繼承適用於:

type TPassenger = class 
    public 
    firstname, lastname: string; 
end; 
type TWeddingGuest = class (TPassenger) 
    public 
    relationship: string; 
end; 

因爲GetManifest()返回的乘客名單,以及所有參加婚禮的客人都是客,你現在可以這樣做:

type TWeddingJump = class (TJump) 
    // ... same as before, but: 
    // replace: procedure GetWeddingManfiest... 
    // with: 
    procedure GetManifest() : TList<TPassenger>; override; 
    // (remember to add the corresponding 'virtual' in TJump) 
end; 

現在,請填寫TWeddingJump.PrintManifestRow的詳細信息,PrintManifest的相同版本適用於TJumpTWeddingJump

還有一個問題:我們宣佈PrintManifestRow(passenger:TPassenger),但我們實際上是通過了TWeddingGuest。這是合法的,因爲TWeddingGuestTPassenger的一個子類...但我們需要獲取.relationship字段,並且TPassenger沒有該字段。

如何編譯器的信任,一個TWeddingJump裏面,你總是會在TWeddingGuest,而不是隻是一個普通的TPassenger通過?你必須確保relationship字段實際上存在。

你不能只是聲明爲TWeddingJupmp.(passenger:TWeddingGuest)因爲子類,你基本上承諾做的所有事情父類可以做的,父類可以處理任何TPassenger

所以,你可以回去檢查用手類型和鑄造它,就像一個無類型指針,但同樣,也有更好的方法來處理這個問題:

  • 多態性的方法:移動PrintManifestRow()方法到TPassenger類(刪除passenger:TPassenger參數,因爲這現在是隱式參數Self),覆蓋該方法TWeddingGuest,然後只有TJump.PrintManifest調用passenger.PrintManifestRow()
  • 泛型類的方法:使TJump本身就是一個泛型類(類型TJump<T:TPassenger> = class),而不是具有GetManifest()回報TList<TPassenger>,你有它返回TList<T>。同樣,PrintManifestRow(passenger:TPassenger)變成PrintManifestRow(passenger:T);。現在你可以說:TWeddingJump = class(TJump<TWeddingGuest>),現在你可以自由地將覆蓋版本聲明爲PrintManifestRow(passenger:TWeddingGuest)

無論如何,這比我想要寫的更多。我希望它有幫助。 :)

+0

謝謝@tangentstorm對我的例程的非常有趣的解釋。不,這不是爲婚禮策劃跳傘,但也許這是我應該探索的利基。 :-)實際上,它是用於族譜,Indi是個體,JumpID是一個字符串來表示一個特定的Indi,但它被稱爲JumpID,因爲它被用作一個超鏈接,您可以點擊這個超鏈接來獲得指示。它最好稱爲IndiID。 – lkessler

+0

你基本上說的是我應該把我的對象提升到像我的Indis這樣的高級實體。然後我可以提供在Indis上運行的程序並隱藏不必要的細節。我明白這一切。那麼我添加一個Event對象。然後我需要以某種方式將事件與Indis聯繫起來。然後我添加一個關係對象。現在我有兩個Indis之間的關係。 3個或更多的Indis之間的關係。與關係相關的事件。我們也將Places添加爲對象。我把所有這些都編入了我成熟的家譜計劃。我如何將這一切轉換爲類? – lkessler

+0

那麼,你不需要一次轉換全部......或者根本不需要。但對於初學者來說,根據記錄的類型尋找你正在調度的地方,並將這些記錄轉換爲類。尋找似乎都處理相同類型的函數,並查看它們是否更適合作爲類的方法。您可以在任何使用記錄的地方使用類實例,並且它們可以一起玩。在上面的例子中,我試圖展示如何在不動搖的情況下進行很多小型重組。這幾乎是我在現實生活中的做法。 – tangentstorm