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