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

lke*_*ler 6 delphi oop class

我几天前发了一个问题,答案告诉我要创建自己的课程.

我是OOP之前的老派程序员,我的编程结构合理,高效且有条理,但除了使用Delphi和第三方对象之外,缺少任何自定义OOPing.

当我开始使用Delphi 2时,我已经看过Delphi的面向对象类是如何工作的,但它们对我的编程背景来说似乎很陌生.我理解他们是如何并且非常适合开发人员在用户界面上设计组件和可视化控件.但我从未发现需要在程序本身的编码中使用它们.

所以现在我再看看15年后的Delphi课程和OOPing.例如,如果我采用的结构如下:

type
  TPeopleIncluded = record
    IndiPtr: pointer;
    Relationship: string;
  end;
var
  PeopleIncluded: TList<TPeopleIncluded>;
Run Code Online (Sandbox Code Playgroud)

然后一个OOP倡导者可能会告诉我把它变成一个类.从逻辑上讲,我认为这将是一个继承自通用TList的类.我猜这会是这样的:

TPeopleIncluded<T: class> = class(TList<T>)
Run Code Online (Sandbox Code Playgroud)

但那就是我陷入困境的地方,并没有很好的指示如何做其余的事情.

当我看一下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;
Run Code Online (Sandbox Code Playgroud)

然后他们对构造函数和过程的定义是:

{ 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;
Run Code Online (Sandbox Code Playgroud)

让我告诉你,对于那些在Delphi中使用OOP多年的人来说,这个"简单"的类定义可能是显而易见的,但对我而言,它只为我提供了数百个未解决的问题,我将如何使用它以及如何使用它.

对我来说,这似乎不是一门科学.它似乎是一种如何最好地将信息结构化为对象的艺术.

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

tan*_*orm 10

对我来说,这似乎不是一门科学.它似乎是一种如何最好地将信息结构化为对象的艺术.

嗯...是的 确实没有很多正式要求.它实际上只是一组工具,可以帮助您组织您的想法,并在此过程中消除大量重复.

然后一个OOP倡导者可能会告诉我把它变成一个类.从逻辑上讲,我认为这将是一个继承自通用TList的类.

实际上,通用容器的重点在于您不必为每种类型的对象创建新的容器类.相反,你要创建一个新的内容类,然后创建一个TList<TWhatever>.

将类实例视为记录的指针.

现在:为什么在使用指针记录时可以使用类?有几个原因:

  • 封装:您可以使用private关键字隐藏实现的某些方面,以便其他开发人员(包括您未来的自己)知道不依赖于可能更改的实现细节,或者对理解该概念并不重要.
  • 多态性:通过为每个记录提供一组指向函数的指针,可以避免许多特殊的调度逻辑.然后,不是case为每种类型的对象做一个大的语句来执行不同的事情,而是循环遍历列表并向每个对象发送相同的消息,然后它跟随函数指针来决定要做什么.
  • 继承:当您开始使用指向函数和过程的指针创建记录时,您会发现您经常遇到需要新的函数调度记录的情况,这非常类似于您已有的记录,除非您需要更改一个或两个过程.子类化只是实现这一目标的一种方便方法.

所以在你的另一篇文章中,你表示你的整体计划如下:

procedure PrintIndiEntry(JumpID: string);
  var PeopleIncluded : TList<...>;
begin      
   PeopleIncluded := result_of_some_loop;
   DoSomeProcess(PeopleIncluded);
end;
Run Code Online (Sandbox Code Playgroud)

我不清楚是什么IndiJumpID意思,所以我假装你的公司做跳伞婚礼,这Indi意味着"个人",并且JumpID是数据库中的主键,表明所有这些人都参加婚礼的航班并计划跳出同一架飞机......了解他们Relationship对幸福的夫妇是非常重要的,这样你就可以给他们正确的彩色降落伞.

显然,这不会与您的域名完全匹配,但由于您在这里提出一般性问题,因此细节并不重要.

另一篇文章中的人试图告诉你(我的猜测)不是用类替换你的列表,而是用一个替换JumpID.

换句话说,您不是传递JumpID给过程并使用它来从数据库中获取人员列表,而是创建一个Jump类.

如果你的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;
Run Code Online (Sandbox Code Playgroud)

所以现在PrintManifest()做你的工作PrintIndyEntry(),但不是计算内联列表,而是调用Self.GetManifest().

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

或者,您的数据库可能会经常更改,或者数据库的TJump长度足以让数据库在其下方发生变化.在这种情况下,GetManifest()每次调用时重建列表...或者您可能添加另一个private值,指示您上次查询的时间,并且仅在信息到期后更新.

关键是,PrintManifest不必关心如何GetManifest工作,因为你已经隐藏了这些信息.

当然,在Delphi中,您可以使用a来完成相同的操作unit,隐藏您的implementation部分中的缓存乘客列表列表.

但是,当实施特定于婚礼派对的功能时,clasess会带来更多的东西:

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

type TWeddingJump = class ( TJump )
  private
    procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
    procedure PrintManifest( ); override;
end;
Run Code Online (Sandbox Code Playgroud)

所以这里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;
Run Code Online (Sandbox Code Playgroud)

现在,你想这样做:

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

但是,你不能,因为GetManifest()回报TList< TPassenger >;TWeddingJump,你需要它返回TList< TWeddingGuest >.

那你怎么能处理呢?

在原始代码中,您有:

IndiPtr: pointer
Run Code Online (Sandbox Code Playgroud)

指向什么?我的猜测是,就像这个例子一样,你有不同类型的个体,你需要它们做不同的事情,所以你只需要使用通用指针,让它指向不同类型的记录,并希望你把它转换为以后是对的.但是类可以为您提供几种更好的方法来解决这个问题:

  • 你可以创建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;
Run Code Online (Sandbox Code Playgroud)

因为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;
Run Code Online (Sandbox Code Playgroud)

而现在,你在细节填充TWeddingJump.PrintManifestRow,和相同版本的PrintManifest两个作品TJumpTWeddingJump.

还有一个问题:我们宣布了,PrintManifestRow(passenger:TPassenger)但我们实际上已经过了一个TWeddingGuest.这是合法的,因为它TWeddingGuest是...的子类TPassenger但我们需要进入该.relationship领域,并且TPassenger没有该领域.

编译器如何信任内部TWeddingJump,你总是会传入一个TWeddingGuest而不仅仅是一个普通的TPassenger?你必须向它保证这个relationship领域实际上就在那里.

你不能只是声明它,TWeddingJupmp.(passenger:TWeddingGuest)因为通过子类化,你基本上承诺做父类可以做的所有事情,而父类可以处理任何 事情TPassenger.

所以你可以回去检查类型并手动检查它,就像一个无类型指针一样,但同样,有更好的方法来处理这个:

  • 多态方法:PrintManifestRow()方法移动到TPassenger类(删除passenger:TPassenger参数,因为这是隐式参数Self),覆盖该方法TWeddingGuest,然后只是TJump.PrintManifest调用passenger.PrintManifestRow().
  • 泛型类方法:使TJump自己成为泛型类(类型TJump<T:TPassenger> = class),而不是GetManifest()返回a TList<TPassenger>,而是返回它TList<T>.同样地,PrintManifestRow(passenger:TPassenger)成为PrintManifestRow(passenger:T); 现在您可以说:TWeddingJump = class(TJump<TWeddingGuest>)现在您可以自由地声明被覆盖的版本PrintManifestRow(passenger:TWeddingGuest).

无论如何,这比我预期的所有这些都要多.我希望它有所帮助.:)