2012-07-01 31 views
4

几天前我发布了a question,答案告诉我要创建自己的类。如何开始使用Delphi创建自己的类?

我是OOP之前的老派程序员,我的编程结构合理,高效和组织良好,但缺少使用Delphi和第三方对象的自定义OOP。

我曾经看过Delphi的面向对象类在开始使用Delphi 2时如何工作,但它们似乎与我的编程背景无关。我明白他们是怎么样,对于设计组件和用户界面上的视觉控件的开发人员来说是非常优秀的。但是我从来没有发现需要在程序本身的编码中使用它们。

所以现在我再看看,15年后,在Delphi的课程和OOPing。如果我取,例如,一个结构,我有,例如:

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

然后一个面向对象的倡导者可能会告诉我,使这一类。从逻辑上讲,我认为这将是一个从通用TList继承的类。我想这将这样进行:

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

但是,这是我卡住,并没有对如何做加时赛剩下的良好指示。

当我看到一些类,德尔福作为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创建类的最佳指令 - 以及如何做到正确的德尔福办法。

+0

你使用记录的代码对我来说看起来很好。 –

+0

如果你还不熟悉OOP,我不会直接使用泛型。泛型和类元类是一些需要“调整”的概念。不是说你应该远离泛型。一点也不。但首先给自己一个“普通”OOP的机会。也许在一些试验项目中。当你创建了自己的TObject后代和TObjectList(普通而不是通用的)后代,并编写了代码以类型安全的方式获取列表中的实例几次后,您将开始感受泛型类中有什么。 –

+0

@David:这只是从Generics.Collections复制的代码,所以它看起来更好。但是我在试图将它应用到从TList 继承的我自己的数据结构的需求方面有很多问题。 – lkessler

回答

8

对我而言,这似乎并不是一门科学。它似乎是一个艺术 如何最好地将您的信息结构化为对象。

嗯......是的。真的没有太多的正式要求。这实际上只是一组工具,可帮助您组织自己的想法,并在此过程中消除大量重复。

然后,一个面向对象的倡导者可能会告诉我要把它做成一个类。从逻辑上讲,我认为这将是一个从通用TList继承的类。

事实上,通用集装箱的整点是,你必须做出新的容器类每种类型的对象。相反,你会创建一个新的内容类,然后创建一个TList<TWhatever>

将类实例想象为指向记录的指针。

现在:当你可以使用指向记录的指针时,为什么要使用类?一对夫妇的原因:

  • 封装:您可以隐藏实现的某些方面与private关键字,以便其他开发者(包括你的未来的自己)知道不依赖于实现细节可能会改变,或者只是AREN了解这个概念并不重要。
  • 多态性:通过为每条记录提供一组函数指针,可以避免大量特殊的调度逻辑。然后,您不必为每种类型的对象执行不同的操作,而是通过遍历列表并向每个对象发送相同的消息,然后遵循函数指针来决定要执行的操作。
  • 继承:当您开始制作记录时,通过指向函数和过程的指针,您会发现您经常遇到需要一个新的函数 - 调度记录,这与您已有的非常相似,除非需要更改或两个程序。子类化只是实现这一目的的一种便捷方式。

因此,在你的其他职位,你指出你的整体方案是这样的:

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

这不是很清楚,我什么IndiJumpID平均,所以我要假装你的公司做跳伞的婚礼,那Indi的意思是“个人”,而JumpID是数据库中的主要关键字,表明所有这些人都参加婚礼派对并且计划跳出同一架飞机的飞行......而且这对于知道他们的Relationship对幸福的夫妇,让你可以给他们正确的颜色降落伞。

很显然,这并不完全符合你的域名,但由于你在这里提出一个普遍的问题,所以细节并不重要。

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

换句话说,不是将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; 

所以现在PrintManifest()可以完成PrintIndyEntry()的工作,但不是在线计算列表,而是调用Self.GetManifest()

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

或者您的数据库可能会频繁更改,或者TJump会持续很长时间,以致数据库可能会在其下更改。在这种情况下,每次调用时,GetManifest()都会重建列表...或者您可能会添加另一个private值,指示您上次查询的时间,并且只在信息过期后才更新。

问题是PrintManifest不需要关心GetManifest是如何工作的,因为你已经隐藏了这些信息。

当然,在Delphi中,您可以使用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; 

所以在这里,在TWeddingJump继承InitTJumpGetManifest,但它还增加了一个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的需求,但这意味着即使你不是在讨论婚礼,方法总是在附近。
  • 您可以在TWeddingGuest课程中添加GetRelationship(guest:TPassenger),只需在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的相同版本适用于TJumpTWeddingJump

还有一个问题:我们宣布PrintManifestRow(passenger:TPassenger),但我们实际上是通过了TWeddingGuest。这是合法的,因为TWeddingGuestTPassenger的一个子类...但我们需要获取.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()回报TList<TPassenger>,你有它返回TList<T>。同样,PrintManifestRow(passenger:TPassenger)变成PrintManifestRow(passenger:T);。现在你可以说:TWeddingJump = class(TJump<TWeddingGuest>),现在你可以自由地将覆盖版本声明为PrintManifestRow(passenger:TWeddingGuest)

无论如何,这比我想要写的更多。我希望它有帮助。 :)

+0

谢谢@tangentstorm对我的例程的非常有趣的解释。不,这不是为婚礼策划跳伞,但也许这是我应该探索的利基。 :-)实际上,它是用于族谱,Indi是个体,JumpID是一个字符串来表示一个特定的Indi,但它被称为JumpID,因为它被用作一个超链接,您可以点击这个超链接来获得指示。它最好称为IndiID。 – lkessler

+0

你基本上说的是我应该把我的对象提升到像我的Indis这样的高级实体。然后我可以提供在Indis上运行的程序并隐藏不必要的细节。我明白这一切。那么我添加一个Event对象。然后我需要以某种方式将事件与Indis联系起来。然后我添加一个关系对象。现在我有两个Indis之间的关系。 3个或更多的Indis之间的关系。与关系相关的事件。我们也将Places添加为对象。我把所有这些都编入了我成熟的家谱计划。我如何将这一切转换为类? – lkessler

+0

那么,你不需要一次转换全部......或者根本不需要。但对于初学者来说,根据记录的类型寻找你正在调度的地方,并将这些记录转换为类。寻找似乎都处理相同类型的函数,并查看它们是否更适合作为类的方法。您可以在任何使用记录的地方使用类实例,并且它们可以一起玩。在上面的例子中,我试图展示如何在不动摇的情况下进行很多小型重组。这几乎是我在现实生活中的做法。 – tangentstorm