如何开始用Delphi创建我自己的类? [英] How to Start Creating My Own Classes with Delphi?

查看:94
本文介绍了如何开始用Delphi创建我自己的类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在几天前发布了一个问题,并回答了我创建了自己的课程。



我是一个老的程序员从前OOP天我的编程结构良好,高效和有组织,但缺乏任何自定义OOPing而不是使用Delphi和第三方对象。



我在开始使用Delphi 2时已经看过Delphi面向对象的类如何工作,但是对于我的编程背景来说,它们似乎很陌生。我理解他们是如何,是非常好的开发人员设计组件和视觉控制的用户界面。但我从来没有发现需要在我的程序本身的编码中使用它们。



现在我再看一遍,15年后,在Delphi的类和OOPing。例如,如果我采用以下结构:

 键入
TPeopleIncluded = record
IndiPtr:pointer;
关系: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);覆盖;
public
constructor Create(AOwnsObjects:Boolean = True);超载;
constructor Create(const AComparer:IComparer< T> ;; AOwnsObjects:Boolean = True);超载;
constructor Create(Collection:TEnumerable< T>; AOwnsObjects:Boolean = True);超载;
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;

过程TObjectList< T> .Notify(const Value:T; Action:TCollectionNotification);
begin
inherited;
如果OwnsObjects和(Action = cnRemoved)then
Value.Free;
end;

让我告诉你,这个简单类定义可能对你使用OOP在德尔福多年,但对我来说,它只提供了数百关于我使用和我如何使用它的未回答的问题。



对我来说,这是似乎不是一门科学。它似乎是一个艺术如何最好地将您的信息结构到对象。



所以这个问题,我希望它不会被关闭,因为我真的需要帮助这是我在哪里或如何获得关于使用Delphi创建类的最佳指导 - 以及如何做到正确的Delphi方法。

解决方案


对我来说,这看起来不是一门科学。它似乎是一个艺术
如何最好地将您的信息结构成对象。


好吧...真的没有很多正式的要求。


然后,一个OOP倡导者可能会告诉我这是一个类。逻辑上,我认为这将是一个继承自通用TList的类。


实际上,通用容器的整体重点是你不必类为每种类型的对象。相反,您将创建一个新的内容类,然后创建 TList



类实例作为指向记录的指针。



现在:为什么要使用一个类,当你可以使用指向记录的指针?有几个原因:




  • 封装:您可以使用 private 关键字,以便其他开发人员(包括您的未来自我)知道不依赖可能更改或对理解概念不重要的实施细节。

  • 多态性:您可以通过为每个记录提供一组指向函数的指针,避免大量特殊的分派逻辑。然后,不是有一个大的 case 语句,你为每种类型的对象做不同的事情,你循环通过你的列表和发送每个对象相同的消息,然后它遵循
  • 继承:当您开始使用指向函数和过程的指针创建记录时,您会发现您经常遇到需要的情况一个新的函数调度记录,非常像一个你已经有,除非你需要更改一个或两个过程。


所以在你的其他帖子中,你表示你的整个程序看起来像这样:

 程序PrintIndiEntry(JumpID:string); 
var PeopleIncluded:TList< ...> ;;
begin
PeopleIncluded:= result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;

我不清楚 Indi JumpID 的意思是,所以我假装你的公司做跳伞婚礼, Indi JumpID 是数据库中的主键,表示一个飞行,所有这些人都在婚礼,并计划跳出同一个飞机...它的生命重要的是要知道他们关系给幸福的夫妇,以便你可以给他们正确的颜色降落伞。



显然,这不符合您的域名,但因为你在这里问一个一般问题,细节并不重要。



其他帖子试图告诉你(我的猜测)不是用一个类替换你的列表,而是用一个替换JumpID。



换句话说,而不是将 JumpID 传递给过程,并使用它从数据库中提取人员列表,您创建了 Jump 类。



如果你的JumpID实际上表示一个跳转,如 goto ,那么你可能实际上



事实上,我们假设你做一些不是婚礼的派对,而且在这种情况下,你不需要Relationships,只需要一个简单的人列表:

  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();虚拟;
end;

现在 PrintManifest() PrintIndyEntry(),但不是计算列表内联,它调用 Self.GetManifest() p>

现在或许你的数据库没有太大变化,你的 TJump 实例总是短暂的,所以你决定在构造函数中填充 Self.manifest 。在这种情况下, GetManifest()只是返回该列表。



code> TJump 停留在足够长的时间,数据库可能会在其下面更改。在这种情况下, GetManifest()每次调用时重建列表...或者你可以添加另一个 private



关键是 PrintManifest don'因为你已经隐藏了这些信息。



当然,在Delphi中,你必须要注意 GetManifest ,您可以使用单位完成相同的操作,在您的实现部分中隐藏高速缓存的乘客列表的列表。



但是clasess带来了更多的表,当它到达实施婚礼方特定功能:

  type TWeddingGuest = record 
public
旅客:TPassenger;
关系:string;
end;

type TWeddingJump = class(TJump)
private
procedure GetWeddingManifest():TList< TWeddingGuest> ;;
procedure PrintManifest();覆盖;
end;

因此, TWeddingJump TJump 中的code> Init 和 GetManifest GetWeddingManifest(); ,它将覆盖 PrintManifest()的一些自定义实现的行为。 (你知道这是因为 override 标记在这里,这对应于 virtual 标记在<$ c $现在,假设 PrintManifest 实际上是一个相当复杂的程序,并且你不想复制所有的代码,当你想做的是在标题中添加一列,并在正文中列出关系字段中的另一列。你可以这样做:

  type TJump = class 
// ...与前面一样,但添加:
procedure PrintManfestHeader(); virtual;
procedure PrintManfiestRow(passenger:TPassenger); virtual;
end;
类型TWeddingJump = class(TJump)
// ...和前面一样,但是:
// *删除PrintManifest覆盖
// * add:
过程PrintManfestHeader(); override;
过程PrintManfiestRow(passenger:TPassenger); override;

end ;

现在,您要这样做:

 过程TJump.PrintManifest()
var乘客:TPassenger;
begin;
// ...
Self.PrintManifestHeader();
for Self.GetManifest()do begin
Self.PrintManifestRow();
end;
// ...
end;

但是你不能,因为 GetManifest c $ c>返回 TList< TPassenger> ;; TWeddingJump ,您需要返回 TList< TWeddingGuest>



好,你该如何处理?



您的原始代码,您有这样:

  IndiPtr:pointer 

指针指向什么?我的猜测是,就像这个例子,你有不同类型的个人,你需要他们做不同的事情,所以你只使用一个通用的指针,让它指向不同种类的记录,并希望你把它正确的事情后。但是类给你几个更好的方法来解决这个问题:




  • 你可以使 TPassenger a类并添加 GetRelationship()方法。这将消除对 TWeddingGuest 的需要,但这意味着 GetRelationship 方法总是在周围,即使你不是

  • 您可以在 TWeddingGuest GetRelationship(guest:TPassenger) c $ c>类,只需调用 TWeddingGuest.PrintManifestRow()



但是假设您必须查询数据库以填充该信息。使用上面的两个方法,你为每个乘客发出一个新的查询,这可能会浪费你的数据库。你真的想要在 GetManifest()中一次性提取所有内容。



再次:

  type TPassenger = class 
public
firstname,lastname:string;
end;
type TWeddingGuest = class(TPassenger)
public
关系:string;
end;

因为 GetManifest()乘客,所有婚礼客人都是乘客,您现在可以这样做:

  type TWeddingJump = class(TJump)
// ...与以前一样,但是:
// replace:procedure GetWeddingManfiest ...
// with:
procedure GetManifest():TList< TPassenger>覆盖;
//(记得在TJump中添加相应的'virtual')
end;

现在,您填写 TWeddingJump.PrintManifestRow TJump TWeddingJump 可以使用相同版本的 PrintManifest / code>。



还有一个问题:我们宣布 PrintManifestRow(passenger:TPassenger) 're实际传入一个 TWeddingGuest 。这是合法的,因为 TWeddingGuest TPassenger 的子类...但是我们需要得到 .relationship 字段, TPassenger 没有该字段。



如何让编译器相信在 TWeddingJump 中,你总是要传递一个 TWeddingGuest ,而不是一个普通的 TPassenger ?你必须确保关系字段实际上存在。



您不能将它声明为 TWeddingJupmp。(乘客:TWeddingGuest),因为通过子类化,基本上承诺做父类可以做的所有事情,父类可以处理任何 TPassenger



因此,你可以回到手工检查类型并转换它,就像一个无类型的指针,但是再次有更好的方法来处理:




  • 多态性方法: PrintManifestRow()方法移动到 TPassenger 类(删除乘客:TPassenger 参数,因为这现在是隐式参数 Self ,覆盖 TWeddingGuest 中的方法,然后只需要 TJump.PrintManifest 调用 passenger.PrintManifestRow ()

  • 通用类方法: make TJump 本身是一个通用类(类型 TJump ; T:TPassenger> = class ),而不是具有 GetManifest() return a TList< TPassenger> code>,您就可以返回 TList< T> 。同样, PrintManifestRow(乘客:TPassenger)成为 PrintManifestRow(乘客:T) ;.现在你可以说: TWeddingJump = class(TJump< TWeddingGuest>),现在你可以声明被覆盖的版本为 PrintManifestRow(乘客:TWeddingGuest )



无论如何,这是比我预期写的更多。我希望它帮助。 :)


I posted a question a few days ago, and the answers told me to create my own classes.

I'm an old-school programmer from the pre-OOP days my programming is well structured, efficient and organized, but lacks in any custom OOPing other than using Delphi and 3rd party objects.

I had looked at how Delphi's object oriented classes worked back when I started using Delphi 2, but they seemed foreign to my programming background. I understand how they were and are excellent for developers designing components and for visual controls on the user interface. But I never found the need to use them in the coding of my program itself.

So now I look again, 15 years later, at Delphi's classes and OOPing. If I take, for example, a structure that I have such as:

type
  TPeopleIncluded = record
    IndiPtr: pointer;
    Relationship: string;
  end;
var
  PeopleIncluded: TList<TPeopleIncluded>;

Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList. I would guess this would be done like this:

TPeopleIncluded<T: class> = class(TList<T>)

But that's where I get stuck, and don't have good instructions on how ot do the rest.

When I look at some class that Delphi has as an example in the Generics.Collections unit, I see:

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;

and then their definitions of the constructors and procedures are:

{ 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;

Let me tell you that this "simple" class definition may be obvious to those of you who have used OOP in Delphi for years, but to me it only provides me with hundreds of unanswered questions on what do I use and how do I use it.

To me, this does not appear to be a science. It appears to be an art of how to best structure your information into objects.

So this question, and I hope it doesn't get closed because I really need help with this, is where or how do I get the best instruction on using Delphi to create classes - and how to do it the proper Delphi way.

解决方案

To me, this does not appear to be a science. It appears to be an art of how to best structure your information into objects.

Well... Yeah. There really aren't a lot of formal requirements. It's really just a set of tools to help you organize your ideas, and eliminate a lot of duplication along the way.

Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList.

Actually, the whole point of generic containers is that you don't have to make a new container class for each type of object. Instead, you'd make a new content class and then create a TList<TWhatever>.

Think of a class instance as a pointers to a record.

Now: why use a class when you could use a pointer to a record? A couple reasons:

  • encapsulation: You can hide some aspects of the implementation with the private keyword so that other developers (including your future self) know not to depend on implementation details that may change or that just aren't important to understanding the concept.
  • polymorphism: You can avoid a lot of special dispatch logic by giving each of your records a set of pointers to functions. Then, rather than having a large case statement where you do different things for each type of object, you loop through your list and send each object the same message, then it follows the function pointer to decide what to do.
  • inheritance: As you start making records with pointers to functions and procedures, you find that you often have cases where you need a new function-dispatch record that's very much like one you already have, except you need to change one or two of the procedures. Subclassing is just a handy way to make that happen.

So in your other post, you indicated that your overall program looks like this:

procedure PrintIndiEntry(JumpID: string);
  var PeopleIncluded : TList<...>;
begin      
   PeopleIncluded := result_of_some_loop;
   DoSomeProcess(PeopleIncluded);
end;

It's not clear to me what Indi or JumpID mean, so I'm going to pretend that your company does skydiving weddings, and that Indi means "individual" and JumpID is a primary key in a database, indicating a flight where all those individuals are in the wedding party and scheduled to jump out of the same plane... And it's vitally important to know their Relationship to the happy couple so that you can give them the right color parachute.

Obviously, that isn't going to match your domain exactly, but since you're asking a general question here, the details don't really matter.

What the people in the other post were trying to tell you (my guess anyway) wasn't to replace your list with a class, but to replace the JumpID with one.

In other words, rather than passing JumpID to a procedure and using that to fetch the list of people from a database, you create a Jump class.

And if your JumpID actually indicates a jump as in goto, then you'd probably actually a bunch of classes that all subclass the same thing, and override the same method in different ways.

In fact, let's assume that you do some parties that aren't weddings, and in that case, you don't need the Relationships, but only a simple list of people:

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() does the job of your PrintIndyEntry(), but instead of calculating the list inline, it calls Self.GetManifest().

Now maybe your database doesn't change much, and your TJump instance is always short lived, so you decide to just populate Self.manifest in the constructor. In that case, GetManifest() just returns that list.

Or maybe your database changes frequently, or the TJump sticks around long enough that the database may change underneath it. In that case, GetManifest() rebuilds the list each time it's called... Or perhaps you add another private value indicating the last time you queried, and only update after the information expires.

The point is that PrintManifest doesn't have to care how GetManifest works, because you've hidden that information away.

Of course, in Delphi, you could have done the same thing with a unit, hiding a list of cached passenger lists in your implementation section.

But clasess bring a little more to the table, when it comes time to implement the wedding-party-specific features:

type TWeddingGuest = record
  public
    passenger    : TPassenger;
    Relationship : string;
end;

type TWeddingJump = class ( TJump )
  private
    procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
    procedure PrintManifest( ); override;
end;

So here, the TWeddingJump inherits the Init and GetManifest from the TJump, but it also adds a GetWeddingManifest( );, and it's going to override the behavior of PrintManifest() with some custom implementation. (You know it's doing this because of the override marker here, which corresponds to the virtual marker in TJump.

But now, suppose that PrintManifest is actually a rather complicated procedure, and you don't want to duplicate all that code when all you want to do is add one column in the header, and another column in the body listing the relationship field. You can do that like so:

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;

Now, you want to do this:

procedure TJump.PrintManifest( )
   var passenger: TPassenger;
begin;
   // ...
   Self.PrintManifestHeader();
   for guest in Self.GetManifest() do begin
      Self.PrintManifestRow();
   end;
   // ...
end;

But you can't, yet, because GetManifest() returns TList< TPassenger >; and for TWeddingJump, you need it to return TList< TWeddingGuest >.

Well, how can you handle that?

In your original code, you have this:

IndiPtr: pointer

Pointer to what? My guess is that, just like this example, you have different types of individual, and you need them to do different things, so you just use a generic pointer, and let it point to different kinds of records, and hope you cast it to the right thing later. But classes give you several better ways to solve this problem:

  • You could make TPassenger a class and add a GetRelationship() method. This would eliminate the need for TWeddingGuest, but it means that GetRelationship method is always around, even when you're not talking about weddings.
  • You could add a GetRelationship(guest:TPassenger) in the TWeddingGuest class, and just call that inside TWeddingGuest.PrintManifestRow().

But suppose you have to query a database to populate that information. With the two methods above, you're issuing a new query for each passenger, and that might bog down your database. You really want to fetch everything in one pass, in GetManifest().

So, instead, you apply inheritance again:

type TPassenger = class
  public
    firstname, lastname: string;
end;
type TWeddingGuest = class (TPassenger)
  public
    relationship: string;
end;

Because GetManifest() returns a list of passengers, and all wedding guests are passengers, you can now do this:

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;

And now, you fill in the details for TWeddingJump.PrintManifestRow, and the same version of PrintManifest works for both TJump and TWeddingJump.

There's still one problem: we declared PrintManifestRow(passenger:TPassenger) but we're actually passing in a TWeddingGuest. This is legal, because TWeddingGuest is a subclass of TPassenger... But we need to get at the .relationship field, and TPassenger doesn't have that field.

How can the compiler trust that inside a TWeddingJump, you're always going to pass in a TWeddingGuest rather than just an ordinary TPassenger? You have to assure it that the relationship field is actually there.

You can't just declare it as TWeddingJupmp.(passenger:TWeddingGuest) because by subclassing, you're basically promising to do all the things the parent class can do, and the parent class can handle any TPassenger.

So you could go back to checking the type by hand and casting it, just like an untyped pointer, but again, there are better ways to handle this:

  • Polymorphism approach: move the PrintManifestRow() method to the TPassenger class (removing the passenger:TPassenger parameter, as this is now the implicit parameter Self), override that method in TWeddingGuest, and then just have TJump.PrintManifest call passenger.PrintManifestRow().
  • Generic class approach: make TJump itself a generic class (type TJump<T:TPassenger> = class), and instead of having GetManifest() return a TList<TPassenger>, you have it return TList<T>. Likewise, PrintManifestRow(passenger:TPassenger) becomes PrintManifestRow(passenger:T);. Now you can say: TWeddingJump = class(TJump<TWeddingGuest>) and now you're free to declare the overridden version as PrintManifestRow(passenger:TWeddingGuest).

Anyway, that's way more than I expected to write about all this. I hope it helped. :)

这篇关于如何开始用Delphi创建我自己的类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆