對我而言,這似乎並不是一門科學。它似乎是一個藝術 如何最好地將您的信息結構化爲對象。
嗯......是的。真的沒有太多的正式要求。這實際上只是一組工具,可幫助您組織自己的想法,並在此過程中消除大量重複。
然後,一個面向對象的倡導者可能會告訴我要把它做成一個類。從邏輯上講,我認爲這將是一個從通用TList繼承的類。
事實上,通用集裝箱的整點是,你不必須做出新的容器類每種類型的對象。相反,你會創建一個新的內容類,然後創建一個TList<TWhatever>
。
將類實例想象爲指向記錄的指針。
現在:當你可以使用指向記錄的指針時,爲什麼要使用類?一對夫婦的原因:
- 封裝:您可以隱藏實現的某些方面與
private
關鍵字,以便其他開發者(包括你的未來的自己)知道不依賴於實現細節可能會改變,或者只是AREN瞭解這個概念並不重要。
- 多態性:通過爲每條記錄提供一組函數指針,可以避免大量特殊的調度邏輯。然後,您不必爲每種類型的對象執行不同的操作,而是通過遍歷列表並向每個對象發送相同的消息,然後遵循函數指針來決定要執行的操作。
- 繼承:當您開始製作記錄時,通過指向函數和過程的指針,您會發現您經常遇到需要一個新的函數 - 調度記錄,這與您已有的非常相似,除非需要更改或兩個程序。子類化只是實現這一目的的一種便捷方式。
因此,在你的其他職位,你指出你的整體方案是這樣的:
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
這不是很清楚,我什麼Indi
或JumpID
平均,所以我要假裝你的公司做跳傘的婚禮,那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
繼承Init
從TJump
GetManifest
,但它還增加了一個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
的相同版本適用於TJump
和TWeddingJump
。
還有一個問題:我們宣佈PrintManifestRow(passenger:TPassenger)
,但我們實際上是通過了TWeddingGuest
。這是合法的,因爲TWeddingGuest
是TPassenger
的一個子類...但我們需要獲取.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)
。
無論如何,這比我想要寫的更多。我希望它有幫助。 :)
你使用記錄的代碼對我來說看起來很好。 –
如果你還不熟悉OOP,我不會直接使用泛型。泛型和類元類是一些需要「調整」的概念。不是說你應該遠離泛型。一點也不。但首先給自己一個「普通」OOP的機會。也許在一些試驗項目中。當你創建了自己的TObject後代和TObjectList(普通而不是通用的)後代,並編寫了代碼以類型安全的方式獲取列表中的實例幾次後,您將開始感受泛型類中有什麼。 –
@David:這只是從Generics.Collections複製的代碼,所以它看起來更好。但是我在試圖將它應用到從TList繼承的我自己的數據結構的需求方面有很多問題。 –
lkessler