我几天前发布了a question,答案告诉我要创建自己的课程。
我是OOP之前的老派程序员,我的编程结构合理,高效且有条理,但除了使用Delphi和第三方对象之外,缺少任何自定义OOPing。
当我开始使用Delphi 2时,我看过Delphi的面向对象类是如何工作的,但它们对我的编程背景来说似乎很陌生。我理解他们是如何并且非常适合开发人员在用户界面上设计组件和可视化控件。但我从未发现需要在程序编码中使用它们。
所以现在我再看看,15年后,在Delphi的课程和OOPing。例如,如果我采用的结构如下:
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
var
PeopleIncluded: TList<TPeopleIncluded>;
然后OOP倡导者可能会告诉我要把它变成一个班级。从逻辑上讲,我认为这将是一个继承自通用TList的类。我猜这会是这样做的:
TPeopleIncluded<T: class> = class(TList<T>)
但那就是我被困住的地方,而且没有很好的指示如何做其余的事情。
当我查看Delphi在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创建类的最佳指令 - 以及如何以正确的Delphi方式做到这一点。
答案 0 :(得分:10)
对我来说,这似乎不是一门科学。这似乎是一门艺术 如何最好地将您的信息结构化为对象。
嗯......是的确实没有很多正式的要求。它实际上只是一组工具,可以帮助您整理您的想法,并在此过程中消除大量重复。
然后OOP倡导者可能会告诉我要把它变成一个班级。从逻辑上讲,我认为这将是一个继承自通用TList的类。
实际上,通用容器的重点在于,您 必须为每种类型的对象创建一个新的容器类。相反,您要制作新的内容类,然后创建TList<TWhatever>
。
将类实例视为记录的指针。
现在:为什么在使用指向记录的指针时使用类?有几个原因:
private
关键字隐藏实现的某些方面,以便其他开发人员(包括您未来的自己)知道不依赖于可能更改的实现细节或理解这个概念并不重要。case
语句来为每种类型的对象执行不同的操作,而是循环遍历列表并向每个对象发送相同的消息,然后按照函数指针来决定要执行的操作。 / LI>
所以在你的另一篇文章中,你表示你的整体计划是这样的:
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
我不清楚Indi
或JumpID
是什么意思,所以我假装贵公司会举行跳伞婚礼,Indi
表示&#34;个别&#34;并且JumpID
是数据库中的主键,表示所有这些人都参加婚礼并计划跳出同一架飞机的航班......了解他们的{至关重要} { {1}}对幸福的情侣,以便你可以给他们正确的彩色降落伞。
显然,这并不能完全匹配您的域名,但由于您在此处提出一般性问题,因此细节并不重要。
另一篇文章中的人试图告诉你(我的猜测无论如何)不是用类替换你的列表,而是用一个替换JumpID。
换句话说,不是将Relationship
传递给某个过程并使用它来从数据库中获取人员列表,而是创建一个JumpID
类。
如果您的JumpID实际上表示像Jump
中那样跳转,那么您实际上可能是一堆所有子类都相同的类,并以不同的方式覆盖相同的方法。
事实上,让我们假设你做了一些不是婚礼的派对,在这种情况下,你不需要关系,但只需要一个简单的人员列表:
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()
值,表示您上次查询的时间,并且仅在信息到期后才更新。 / p>
关键是,private
并不关心PrintManifest
的工作原理,因为您已将这些信息隐藏起来。
当然,在Delphi中,您可以使用GetManifest
完成相同的操作,隐藏unit
部分中的缓存旅客列表列表。
但是,当实施特定于婚礼派对的功能时,clasess会带来更多的东西:
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
和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)
方法总是存在,即使您不是在谈论婚礼。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
字段,TWeddingJump
没有该字段
编译器如何相信TWeddingGuest
内的TPassenger
总是传递relationship
而不是普通的TWeddingJupmp.(passenger:TWeddingGuest)
?您必须确保TPassenger
字段实际存在。
您不能将其声明为PrintManifestRow()
,因为通过子类化,您基本上承诺完成父类可以执行的所有操作,并且父类可以处理任何 TPassenger
。
所以你可以回去手工检查类型并投射它,就像一个无类型指针一样,但同样,有更好的方法来处理这个:
passenger:TPassenger
方法移至Self
类(删除TWeddingGuest
参数,因为现在这是隐式参数{{1} }),在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)
现在您可以将被覆盖的版本声明为{{1}}。无论如何,这种方式比我预期写的更多。我希望它有所帮助。 :)