如何用Delphi开始创建自己的类?

时间:2012-07-01 18:38:38

标签: delphi oop class

我几天前发布了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方式做到这一点。

1 个答案:

答案 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;

我不清楚IndiJumpID是什么意思,所以我假装贵公司会举行跳伞婚礼,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中的InitGetManifest,但它还添加了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适用于TWeddingJumpPrintManifestRow(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}}。

无论如何,这种方式比我预期写的更多。我希望它有所帮助。 :)