对我来说,这似乎不是一门科学。看起来是一门艺术
如何最好地将信息构建为对象。
嗯...是的。实际上没有太多正式的要求。它实际上只是一组帮助您组织想法并消除大量重复的工具。
那么 OOP 倡导者可能会告诉我将其作为一个类。从逻辑上讲,我认为这将是一个从通用 TList 继承的类。
事实上,通用容器的全部意义在于你don't必须为每种类型的对象创建一个新的容器类。相反,您将创建一个新的内容类,然后创建一个TList<TWhatever>
.
将类实例视为指向记录的指针。
现在:当您可以使用指向记录的指针时,为什么还要使用类呢?有几个原因:
-
封装:您可以使用以下命令隐藏实现的某些方面
private
关键字,以便其他开发人员(包括未来的您)知道不要依赖可能更改的实现细节,或者对于理解该概念并不重要的实现细节。
-
多态性:通过为每个记录提供一组指向函数的指针,您可以避免许多特殊的调度逻辑。然后,而不是拥有一个大的
case
语句中,您对每种类型的对象执行不同的操作,循环遍历列表并向每个对象发送相同的消息,然后它遵循函数指针来决定要做什么。
-
遗产:当您开始使用指向函数和过程的指针创建记录时,您会发现经常需要一个新的函数分派记录,该记录与您已有的记录非常相似,只是您需要更改一两个过程。子类化只是实现这一目标的一种便捷方法。
因此,在您的另一篇文章中,您指出您的整体程序如下所示:
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
我不清楚什么Indi
or JumpID
我的意思是,所以我要假装你们公司举办跳伞婚礼,然后Indi
意思是“个人”并且JumpID
是数据库中的主键,表示所有这些人都参加婚礼并计划从同一架飞机跳出的航班......了解他们的情况至关重要Relationship
送给这对幸福的夫妇,这样您就可以给他们合适颜色的降落伞。
显然,这不会与您的域名完全匹配,但由于您在这里问的是一般性问题,因此细节并不重要。
另一篇文章中的人试图告诉您的(无论如何我的猜测)不是用类替换您的列表,而是用一个类替换 JumpID。
换句话说,而不是通过JumpID
到一个过程并使用它从数据库中获取人员列表,您创建一个Jump
class.
如果你的 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;
So now PrintManifest()
做你的工作PrintIndyEntry()
,但它不是内联计算列表,而是调用Self.GetManifest()
.
现在也许您的数据库没有太大变化,并且您的TJump
实例总是短暂的,所以你决定只填充Self.manifest
在构造函数中。在这种情况下,GetManifest()
只是返回该列表。
或者也许您的数据库经常更改,或者TJump
保留的时间足够长,以至于其下面的数据库可能会发生变化。在这种情况下,GetManifest()
每次调用时都会重建列表...或者也许您添加另一个private
表示您最后一次查询的值,并且仅在信息过期后更新。
重点是PrintManifest
不必关心如何GetManifest
有效,因为您已经隐藏了该信息。
当然,在 Delphi 中,您可以使用unit
,隐藏您的缓存乘客列表列表implementation
部分。
但是,当需要实现婚礼派对特定的功能时,类会带来更多的东西:
type TWeddingGuest = record
public
passenger : TPassenger;
Relationship : string;
end;
type TWeddingJump = class ( TJump )
private
procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
procedure PrintManifest( ); override;
end;
所以在这里,TWeddingJump
继承了Init
and GetManifest
来自TJump
,但它还添加了一个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
,但这意味着GetRelationship
方法总是存在的,即使你不是在谈论婚礼。
- 你可以添加一个
GetRelationship(guest:TPassenger)
in the TWeddingGuest
类,然后在里面调用它TWeddingGuest.PrintManifestRow()
.
但假设您必须查询数据库来填充该信息。使用上述两种方法,您要为每位乘客发出新的查询,这可能会使您的数据库陷入困境。你真的想一次性获取所有内容GetManifest()
.
因此,您再次应用继承:
type TPassenger = class
public
firstname, lastname: string;
end;
type TWeddingGuest = class (TPassenger)
public
relationship: string;
end;
Because 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
and TWeddingJump
.
还有一个问题:我们声明了PrintManifestRow(passenger:TPassenger)
但我们实际上传递的是TWeddingGuest
。这是合法的,因为TWeddingGuest
是一个子类TPassenger
...但我们需要了解.relationship
场,以及TPassenger
没有那个字段。
编译器如何信任 a 中的内容TWeddingJump
,你总是会传递一个TWeddingGuest
而不仅仅是一个普通的TPassenger
?你必须保证relationship
场其实就在那里。
你不能只是将其声明为TWeddingJupmp.(passenger:TWeddingGuest)
因为通过子类化,你基本上承诺做父类可以做的所有事情,并且父类可以处理any TPassenger
.
因此,您可以返回手动检查类型并对其进行强制转换,就像非类型化指针一样,但同样,有更好的方法来处理此问题:
-
多态性方法:移动
PrintManifestRow()
方法到TPassenger
类(删除passenger:TPassenger
参数,因为这现在是隐式参数Self
),重写该方法TWeddingGuest
,然后就有TJump.PrintManifest
call passenger.PrintManifestRow()
.
-
通用类方法: make
TJump
本身是一个泛型类(类型TJump<T:TPassenger> = class
),而不是有GetManifest()
返回一个TList<TPassenger>
,你已经返回了TList<T>
。同样地,PrintManifestRow(passenger:TPassenger)
变成PrintManifestRow(passenger:T)
;.现在你可以说:TWeddingJump = class(TJump<TWeddingGuest>)
现在您可以自由地将覆盖版本声明为PrintManifestRow(passenger:TWeddingGuest)
.
不管怎样,这比我预期要写的要多得多。我希望它有帮助。 :)