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

2024-02-08

我已经发布一个问题 https://stackoverflow.com/questions/11270972/declare-locally-or-globally-in-delphi几天前,答案告诉我创建自己的课程。

我是一名来自 OOP 时代之前的老派程序员,我的编程结构良好、高效且有组织,但除了使用 Delphi 和第 3 方对象之外,缺乏任何自定义 OOP。

当我开始使用 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 方式做到这一点。


对我来说,这似乎不是一门科学。看起来是一门艺术 如何最好地将信息构建为对象。

嗯...是的。实际上没有太多正式的要求。它实际上只是一组帮助您组织想法并消除大量重复的工具。

那么 OOP 倡导者可能会告诉我将其作为一个类。从逻辑上讲,我认为这将是一个从通用 TList 继承的类。

事实上,通用容器的全部意义在于你don't必须为每种类型的对象创建一个新的容器类。相反,您将创建一个新的内容类,然后创建一个TList<TWhatever>.

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

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

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

因此,在您的另一篇文章中,您指出您的整体程序如下所示:

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

我不清楚什么Indi or JumpID我的意思是,所以我要假装你们公司举办跳伞婚礼,然后Indi意思是“个人”并且JumpID是数据库中的主键,表示所有这些人都参加婚礼并计划从同一架飞机跳出的航班......了解他们的情况至关重要Relationship送给这对幸福的夫妇,这样您就可以给他们合适颜色的降落伞。

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

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

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

如果你的 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;

So now PrintManifest()做你的工作PrintIndyEntry(),但它不是内联计算列表,而是调用Self.GetManifest().

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

或者也许您的数据库经常更改,或者TJump保留的时间足够长,以至于其下面的数据库可能会发生变化。在这种情况下,GetManifest()每次调用时都会重建列表...或者也许您添加另一个private表示您最后一次查询的值,并且仅在信息过期后更新。

重点是PrintManifest不必关心如何GetManifest有效,因为您已经隐藏了该信息。

当然,在 Delphi 中,您可以使用unit,隐藏您的缓存乘客列表列表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 and 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) in the TWeddingGuest类,然后在里面调用它TWeddingGuest.PrintManifestRow().

但假设您必须查询数据库来填充该信息。使用上述两种方法,您要为每位乘客发出新的查询,这可能会使您的数据库陷入困境。你真的想一次性获取所有内容GetManifest().

因此,您再次应用继承:

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

Because 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 and TWeddingJump.

还有一个问题:我们声明了PrintManifestRow(passenger:TPassenger)但我们实际上传递的是TWeddingGuest。这是合法的,因为TWeddingGuest是一个子类TPassenger...但我们需要了解.relationship场,以及TPassenger没有那个字段。

编译器如何信任 a 中的内容TWeddingJump,你总是会传递一个TWeddingGuest而不仅仅是一个普通的TPassenger?你必须保证relationship场其实就在那里。

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

因此,您可以返回手动检查类型并对其进行强制转换,就像非类型化指针一样,但同样,有更好的方法来处理此问题:

  • 多态性方法:移动PrintManifestRow()方法到TPassenger类(删除passenger:TPassenger参数,因为这现在是隐式参数Self),重写该方法TWeddingGuest,然后就有TJump.PrintManifest call passenger.PrintManifestRow().
  • 通用类方法: make TJump本身是一个泛型类(类型TJump<T:TPassenger> = class),而不是有GetManifest()返回一个TList<TPassenger>,你已经返回了TList<T>。同样地,PrintManifestRow(passenger:TPassenger)变成PrintManifestRow(passenger:T);.现在你可以说:TWeddingJump = class(TJump<TWeddingGuest>)现在您可以自由地将覆盖版本声明为PrintManifestRow(passenger:TWeddingGuest).

不管怎样,这比我预期要写的要多得多。我希望它有帮助。 :)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何开始使用 Delphi 创建我自己的类? 的相关文章

  • 将结构更改为密封类时隐式转换失败

    有问题的结构 类 public struct HttpMethod public static readonly HttpMethod Get new HttpMethod GET public static readonly HttpMe
  • 如何重载泛型方法以使其在 Typescript 中不那么泛型?

    我在抽象类中有一个通用静态方法 abstract class Base static find
  • 继承php中的属性

    我有一个超类 其中包含用于设置它们的属性和方法 class Super private property function construct set this gt property set 然后我有一个需要使用该属性的子类 class
  • matlab中类库的全局变量

    我有一些matlab声明的类 我如何声明所有类中都可见的常量 例如 这些常量可以是在所有类的方法中使用的物理常量 首先想到的是使用全局变量 还有更好的办法吗 最好在单独的文件中声明这些常量 包含常量的类是执行此操作的一种很好的干净方法 请参
  • 我如何使用 cout << myclass

    myclass是我写的一个C 类 当我写的时候 myclass x cout lt lt x 我该如何输出10 or 20 2 就像一个integer or a float value 通常通过重载operator lt lt 对于你的班级
  • 抽象类、接口、mixins

    有人可以向我解释一下两者之间的区别吗抽象类 接口 and mixins 我之前在代码中使用过它们 但我不知道技术差异 抽象类 抽象类是不被设计为实例化的类 抽象类可以没有实现 部分实现或全部实现 抽象类旨在允许其子类共享公共 默认 实现 抽
  • 如何在两个类之间共享数据

    Question 有没有一种方法可以让两个类同时相互继承 背景 我目前正在开发一个 Socket Server 项目 在这个项目中 我有两个课程 一个Server类 以及一个GUI班级 他们的目的是不言自明的 但是 我显然需要让两个班级相互
  • 有人用CrossKylix进行真正的跨平台开发吗?

    新版本克罗斯凯利克斯 http crosskylix untergrund net 两周前更新过 即使 Kylix 已经停产很久了 但它似乎仍然被一些 Delphi 开发人员使用 有人在 Windows 和 Linux 的跨平台开发中成功使
  • Python 类 __div__ 问题

    元组代表分数 我试图通过乘以倒数来除以分数 class Test def init self self x 1 2 def div self div fraction return self x 0 div fraction 1 self
  • 创建一个行为类似于任何变量但具有更改/读取回调的类

    我想创建一个类 其行为类似于 python 变量 但在更改 读取 变量 时调用一些回调函数 换句话说 我希望能够按如下方式使用该类 x myClass change callback read callback 将 x 定义为 myclas
  • Word 2010 自动化:“转到书签”

    我有一个用 Delphi 7 编写的程序 它打开一个基于模板的新 Word 文档 文档打开后 系统会自动跳转到书签 在模板中预定义 并在其中添加一些文本 以下代码在 Word 2003 中工作正常 但会导致invalid variant o
  • 我应该使用课程吗? (Python)

    我正在尝试编写一个包含一些数学函数的小型 Python 模块 例如 它可能包含如下函数 def quad x a b c return a x 2 b x c 您可能会注意到它包含几个参数 即a b c 除了变量x 现在 如果我将其放入文件
  • 我可以使用子接口重新编译公共 API 并保持二进制兼容性吗?

    我有一个公共 API 在多个项目中多次使用 public interface Process
  • 在 Objective C 的类方法中引用类本身

    我希望我没有跳过 ObjC 手册中的这一部分 但是是否可以从类的一个类方法中引用该类 就像在 PHP 中一样 您将使用 this 来引用当前实例 而 self 引用实例的类 this 的 ObjC 等价物将是 self 那么 PHP 的 s
  • Delphi 2009 IDE结构视图折叠功能

    现在有谁知道折叠 Delphi 2009 IDE 结构视图中的所有项目吗 我不知道折叠所有项目 这使我很难从视图中找到我的课程 Thanks 选择根节点 类 并按数字键盘上的 除号 那会让一切崩溃 然后按数字键盘上的 加号 键 第一级将展开
  • delphi中如何实现多重继承?

    我正在对一个旧库进行完全重写 我不确定如何处理这种情况 为了便于理解 大家都欢呼自行车类比 我有以下课程 TBike 自行车本身 TBikeWheel 自行车的一个轮子 TBikeWheelFront and TBikeWheelBack
  • Delphi AES 库 (Rijndael) 使用 KAT Vectors 进行测试

    对于这两个库 Delphi 加密概要 v 5 2 http code google com p delphidec TurboPower 密码箱 v 2 07 http lockbox seanbdurkin id au tiki list
  • 在 Delphi 中编程延迟的最佳方法是什么?

    我正在开发的 Delphi 应用程序必须延迟一秒 有时甚至两秒 我想使用最佳实践来对此延迟进行编程 在阅读 stackoverflow 上有关 Delphi Sleep 方法的条目时 我发现了以下两条评论 我遵循这样的格言 如果你觉得需要使
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 如何调试仅在应用程序关闭时发生的崩溃? (德尔福)

    因此 经过最近的一些更改 我们发现我们最古老的应用程序之一有时会在关闭时崩溃 这会以 运行时错误 216 消息的形式或来自 Windows 错误报告的消息的形式表明应用程序已停止工作 该应用程序已经发出OutputDebugString 每

随机推荐

  • 控制器如何手动设置某个字段的验证错误

    我有一个包含 3 个 ActiveRecord 字段的表单 其中一个字段有一种愚蠢的 依赖于国家的验证要求 例如 如果在设置向导表单上创建对象 我仅验证该字段 在我的 POST 处理程序中创建对象时 我想我可以调用errors add 来插
  • python 中的 Mechanizer - 选择没有名称的表单字段

    我有一个类似的问题选择机械化表单中的未命名文本字段 python https stackoverflow com questions 4787907 selecting an unnamed text field in a mechaniz
  • 使用 C++11 基于范围的正确方法是什么?

    使用 C 11 基于范围的正确方法是什么for 应该使用什么语法 for auto elem container or for auto elem container or for const auto elem container 或者其
  • SQL Server 不区分大小写的排序规则

    在 SQL Server 中使用不区分大小写的排序规则有哪些优点 缺点 就查询性能而言 我有一个数据库当前正在使用不区分大小写的排序规则 但我不太喜欢它 我非常想将其更改为区分大小写 更改排序规则时应该注意什么 如果更改数据库上的排序规则
  • 需要有关奇怪的 java.net.HttpURLConnection 行为的帮助

    我正在尝试使用 HttpURLConnection 下载 jpg 但遇到了一个非常奇怪的错误 这是网址 http www vh1 com sitewide promoimages shows m my antonio video super
  • 如何在 angularjs 中添加悬停元素的延迟?

    我有一个元素 span Hover Me span div class outerDiv p Some content p div class innerDiv p More Content p div div 这是JS mouseente
  • 即使安装包后,R 也找不到包

    我一直与zoo包 我很久以前就安装了 今天 我创建了一个新的 R 脚本 并运行library zoo 并得到以下错误 gt library zoo Error in library zoo there is no package calle
  • 暴力破解 PBKDF2 的速度大约有多快?

    在 linkedin 密码哈希泄露之后 我一直在研究我们的密码哈希 我们使用 Django 1 4 它使用 PBKDF2 这很棒 比之前的 SHA1 更进一步 然而我很好奇人们如何轻松地暴力破解这一点 我正在查看我们的密码复杂性规则 并且想
  • 使用 ES6 类的快速路由

    因此 以下代码在开发中有效 但在生产环境中运行时失败 并出现错误TypeError Router use requires middleware function but got a Object 到目前为止 我一定已经尝试了大约一百种不同
  • 从结构体数组中删除元素

    这可能是一个超级简单的问题 但它是 我有一个 结构数组 以及一个要删除的结构数组索引向量 例如 如果我有一个删除向量 2 6 这意味着我想删除数组中的第二个和第六个结构 并且数组将短 2 个元素 干净 简单的 matlab 方法是什么 如果
  • 在 JavaScript 中,如何在超时中包装承诺?

    使用 deferred promise 实现某些异步函数的超时是一种常见的模式 Create a Deferred and return its Promise function timeout funct args time var df
  • 如何在 geom_dotplot 中使用颜色?

    我有这个点图 ggplot mpg aes drv hwy geom dotplot binwidth 1 binaxis y stackdir center 呈现为 我想按制造商对点进行着色 如果我添加一个fill审美的 ggplot m
  • 在没有故事板的情况下创建和执行 Segue

    我有一个没有故事板的应用程序 所有 UI 创建都是用代码完成的 我得到了splitView我想让它在 iPhone 上使用 因为该应用程序最初是为 iPad 设计的 因此当您在主视图中选择列表中的一行时 它在 iPhone 上不会执行任何操
  • 为什么 Z3 中的运算符“/”和“div”给出不同的结果?

    我试图用两个整数来表示一个实数 并将它们用作实数的分子和分母 我写了以下程序 declare const a Int declare const b Int declare const f Real assert f a b assert
  • Visual Studio 2012 - 数据库项目 - 设置默认的发布配置文件

    只是想知道是否有人知道如何将保存的发布配置文件设置为默认配置文件 即当我选择发布时自动加载的配置文件 我喜欢新的配置文件方法 但在开发过程中我有点不愿意一遍又一遍地重新选择相同的配置文件 在 VS2012 及更高版本中 您可以通过右键单击
  • Tensorflow 模型导入到 Java

    我一直在尝试在 Java 中导入和使用我训练过的模型 Tensorflow Python 我能够在 Python 中保存模型 但当我尝试在 Java 中使用相同的模型进行预测时遇到问题 Here https gist github com
  • 在 IB 中使用自动布局,如何以编程方式将 UIView 放置在屏幕中央?

    我有一个 UIView 我想在主视图上水平居中 然后在主视图上垂直居中 减去大约 14 像素 如果我用 IB 设置它 它可以在 Retina 3 5 上运行 但在 Retina 4 上运行时 它当然会偏离大约 40 像素 我认为最好的解决方
  • 请求被中止:无法创建 SSL/TLS 安全通道。

    我想实现 Paypal dodirect 方法 让用户可以直接在我的网站上付款 而不是重定向到用户 因此我已将此 URL 添加为https www sandbox paypal com wsdl PayPalSvc wsdl https w
  • 安全注意事项 - ChromeDriver - Chrome 的 Webdriver

    我想知道是否有人了解有关此声明所涉及的使用 chromedriver 的具体风险的更多信息 如果可能 请使用无法访问敏感本地或网络数据的测试帐户运行 ChromeDriver ChromeDriver 永远不应该使用特权帐户运行 想知道使用
  • 如何开始使用 Delphi 创建我自己的类?

    我已经发布一个问题 https stackoverflow com questions 11270972 declare locally or globally in delphi几天前 答案告诉我创建自己的课程 我是一名来自 OOP 时代