对我而言,这似乎并不是一门科学。它似乎是一个艺术 如何最好地将您的信息结构化为对象。
嗯......是的。真的没有太多的正式要求。这实际上只是一组工具,可帮助您组织自己的想法,并在此过程中消除大量重复。
然后,一个面向对象的倡导者可能会告诉我要把它做成一个类。从逻辑上讲,我认为这将是一个从通用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