如何开始使用 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 class TestData public string Name get set public string type get set public List
  • 为什么非空槽不能与 int、tuple、bytes 子类一起使用?

    参考手册中明确记录了这一点 非空 slots 不适用于从 可变长度 内置类型 例如 int bytes 和 tuple 派生的类 情况确实如此 写道 class MyInt int slots spam 结果是 TypeError none
  • 在Java中从控制台打开包中的类

    因此 当我尝试从命令提示符打开一个不在包中的 java 类时 一切正常 但是当我尝试打开一个包中的类时 它会给我 NoClassDefFoundError 错误 当我尝试打开类 java somepackage someclass 时列出包
  • 通俗地说,Ruby on Rails ORM 是什么?请解释

    我无法理解 Ruby on Rails 中的 ORM 据我了解 表 列和对象 属性之间存在 1 1 的关系 所以每条记录都是一个对象 另外 模型到底是什么 我知道它映射到一张桌子 我真正追求的是对上述内容有更深入的理解 预先感谢您的帮助 我
  • 如何检查文件是否有备用数据流?

    Delphi 有没有办法检查文件是否有任何备用数据流 看一下 Win32 APIFindFirstStreamW https msdn microsoft com en us library windows desktop aa364424
  • CSS class 和 id 同名

    css class 和 id 同名有什么问题吗 就像文章 帖子页脚的 footer 和页面页脚的 footer 一样 不 完全可以接受 一个类是使用 a 来定义的 并且 ID 是使用定义的 因此 就浏览器而言 它们是两个完全独立的项目 唯一
  • Vista 中的文本转语音

    我通过在 2000 NT XP 中使用 Delphi 创建 OLE 对象来做到这一点 如下所示 Voice CreateOLEObject SAPI SpVoice Voice speak 但这在 Vista 中不起作用 我怎样才能让我的程
  • UML 只能用于面向对象吗?

    目前 在我的论文期间 存在着关于 UML 是否仅用于 OO 开发 的激烈争论 我从一些来源进行了搜索 其中给出了很多关于它的论点 就我自己而言 我相信并非所有 UML 图都是专门为 OO 系统 软件开发而制作的 即 状态图 因此 我希望得到
  • 更改代码使用指针实现多对多关系

    我在 Movie hpp 中有以下代码 ifndef MOVIE H define MOVIE H class Movie private std string title public std string getTitle const
  • 如何使用 IdTCPClient 等待来自服务器的字符串?

    我的 IdTelnet indy 10 1 有问题 我无法以 Unicode 模式从服务器读取数据 现在我想用 IdTCPClient 编写 telnet 终端 服务器有时发送一行 有时发送越来越多的行 但发送之间没有固定的时间 现在我的问
  • 如何获取Python对象父级?

    所以 我试图获取自定义对象 内部 的对象 这是一个例子 假设 o 是一个对象 无论是什么类型 它都可以存储变量 o Object class Test def init self self parent o This is where I
  • 如何访问带有美元符号的 PHP 对象属性?

    我有一个 PHP 对象 其属性中有一个美元 符号 如何访问该属性的内容 例子 echo object gt variable Ok echo object gt variable WithDollar Syntax error With 变
  • 显示带有 id 的内部连接的名称[重复]

    这个问题在这里已经有答案了 我有这个查询 select from countrysegments inner join country on countrysegments country id country id inner join
  • 动态重新定义 PHP 类函数?

    我试图弄清楚如何动态导入大量 PHP 类函数 例如 class Entity public function construct type require once type functions php person new Entity
  • 如何将 Delphi Chromium Embedded (TChromium) 组件的声音静音?

    我需要将某个声音静音TChromium组件来制作无声浏览器 主要问题是在 Windows XP 上 当我将浏览器的声音静音时 它会静音整个系统的声音 有没有办法让电脑静音TChromium成分 我已经成功使用以下代码 procedure C
  • 具有默认参数的Python类构造函数[重复]

    这个问题在这里已经有答案了 可能的重复 Python 中的 最不令人惊讶 可变默认参数 https stackoverflow com questions 1132941 least astonishment in python the m
  • 如果您不在 Java 中进行克隆,那么您会做什么以及如何称呼它?

    有没有人对 Java 中的复制构造函数 工厂方法等有任何建议或已建立的最佳实践和命名约定 特别是 假设我有一堂课Thing我想要一个返回新值的方法Thing与 a 具有相同的值Thing传入 如果是实例方法 则作为实例 您会将其作为构造函数
  • Exit() 时是否调用基本对象析构函数?

    我意识到这个问题已经出现过几次 但我试图获得上述问题的明确答案 但我不断遇到相互矛盾的信息 我需要知道的是 当我使用 exit 时 基本类对象是否被破坏 我知道需要删除动态内存 但我的意思更像是 include
  • 在构造函数中运行代码的不好做法可能会失败?

    我的问题更像是一个设计问题 在 Python 中 如果 构造函数 中的代码失败 则该对象最终不会被定义 因此 someInstance MyClass test123 lets say that constructor throws an
  • delphi专家中的第三方依赖

    我正在编写一个delphi ide Expert 带有一些第三方依赖项 视觉控件 我的问题是当这个专家将安装在目标机器上时 这台电脑是否也需要安装这些第三方组件 或者组件是embeded在生成的 bpl 内部 它们将依赖于您放置在包的 re

随机推荐

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

    我有一个包含 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 时代