我读过的官方文档,我理解的类引用是什么的,但我看不到的的时间和原因的他们相比,方案最好的解决方案。
文档中引用的示例是TCollection,可以使用TCollectionItem的任何后代进行实例化。使用类引用的理由是它允许您在编译时调用类型未知的类方法(我假设这是TCollection的编译时间)。我只是没有看到使用TCollectionItemClass作为参数如何优于使用TCollectionItem。 TCollection仍然能够保存TCollectionItem的任何后代,并且仍然能够调用TCollectionItem中声明的任何方法。不是吗?
将此与通用集合进行比较。 TObjectList似乎提供与TCollection相同的功能,并具有类型安全性的附加好处。您无需继承TCollectionItem以存储对象类型,您可以根据需要将集合设置为特定类型。如果您需要从集合中访问项目的成员,则可以使用通用约束。除了在Delphi 2009之前程序员可以使用类引用这一事实之外还有其他令人信服的理由将它们用于通用容器吗?
文档中给出的另一个示例是将类引用传递给充当对象工厂的函数。在这种情况下,工厂用于创建TControl类型的对象。它不是很明显,但我假设TControl工厂正在调用传递给它的后代类型的构造函数而不是TControl的构造函数。如果是这种情况,那么我开始至少看到使用类引用的一些原因。
所以我想我真的想了解是在何时何地类引用是最合适的,什么?他们的购买的开发者?
答案 0 :(得分:22)
MetaClasses都是关于“类程序”的。从基本class
开始:
type
TAlgorithm = class
public
class procedure DoSomething;virtual;
end;
因为DoSomething
是class procedure
,我们可以在没有TAlgorithm实例的情况下调用它(它的行为与任何其他全局过程一样)。我们可以这样做:
TAlgorithm.DoSomething; // this is perfectly valid and doesn't require an instance of TAlgorithm
一旦我们得到这个设置,我们可能会创建一些替代算法,所有算法都共享基本算法的一些位和和平。像这样:
type
TAlgorithm = class
protected
class procedure DoSomethingThatAllDescendentsNeedToDo;
public
class procedure DoSomething;virtual;
end;
TAlgorithmA = class(TAlgorithm)
public
class procedure DoSomething;override;
end;
TAlgorithmB = class(TAlgorithm)
public
class procedure DoSomething;override;
end;
我们现在有一个基类和两个后代类。以下代码完全有效,因为我们将方法声明为“类”方法:
TAlgorithm.DoSomething;
TAlgorithmA.DoSomething;
TAlgorithmB.DoSomething;
让我们介绍class of
类型:
type
TAlgorithmClass = class of TAlgorithm;
procedure Test;
var X:TAlgorithmClass; // This holds a reference to the CLASS, not a instance of the CLASS!
begin
X := TAlgorithm; // Valid because TAlgorithmClass is supposed to be a "class of TAlgorithm"
X := TAlgorithmA; // Also valid because TAlgorithmA is an TAlgorithm!
X := TAlgorithmB;
end;
TAlgorithmClass是一种可以像任何其他数据类型一样使用的数据类型,它可以存储在变量中,作为参数传递给函数。换句话说,我们可以这样做:
procedure CrunchSomeData(Algo:TAlgorithmClass);
begin
Algo.DoSomething;
end;
CrunchSomeData(TAlgorithmA);
在这个例子中,程序CrunchSomeData可以使用算法的任何变体,只要它是TAlgorithm的后代。
这是一个如何在现实世界的应用程序中使用此行为的示例:想象一下工资单类型的应用程序,其中一些数字需要根据法律定义的算法计算。可以想象这个算法会及时改变,因为法律有时会改变。我们的应用程序需要使用旧版本的算法计算当前年度(使用最新计算器)和其他年份的工资。这是事情的样子:
// Algorithm definition
TTaxDeductionCalculator = class
public
class function ComputeTaxDeduction(Something, SomeOtherThing, ThisOtherThing):Currency;virtual;
end;
// Algorithm "factory"
function GetTaxDeductionCalculator(Year:Integer):TTaxDeductionCalculator;
begin
case Year of
2001: Result := TTaxDeductionCalculator_2001;
2006: Result := TTaxDeductionCalculator_2006;
else Result := TTaxDeductionCalculator_2010;
end;
end;
// And we'd use it from some other complex algorithm
procedure Compute;
begin
Taxes := (NetSalary - GetTaxDeductionCalculator(Year).ComputeTaxDeduction(...)) * 0.16;
end;
Delphi构造函数就像“类函数”一样工作;如果我们有一个元类,并且元类知道虚拟构造函数,我们就能够创建后代类型的实例。当您点击“添加”按钮时,TCollection的IDE编辑器会使用它来创建新项目。所有TCollection需要让这个工作是TCollectionItem的MetaClass。
答案 1 :(得分:8)
是的,Collection仍然可以保存TCollectionItem的任何后代并在其上调用方法。但是,它无法实例化TCollectionItem的任何后代的新实例。调用TCollectionItem.Create构造TCollectionItem的实例,而
private
FItemClass: TCollectionItemClass;
...
function AddItem: TCollectionItem;
begin
Result := FItemClass.Create;
end;
将构造一个TCollectionItem后代类在FItemClass中保存的实例。
我对通用容器做的不多,但我认为如果有选择,我会选择通用容器。但是在任何一种情况下,如果我想让列表实例化并且在容器中添加项目时需要做任何其他事情,我仍然必须使用元类,并且我事先并不知道确切的类。
例如,可观察的TObjectList后代(或通用容器)可能具有如下内容:
function AddItem(aClass: TItemClass): TItem;
begin
Result := Add(aClass.Create);
FObservers.Notify(Result, cnNew);
...
end;
我想简而言之,元类的优点/好处是任何只知道
的方法/类type
TMyThing = class(TObject)
end;
TMyThingClass = class of TMyThing;
能够构建任何TMyThing后代的实例,无论它们是否已被声明。
答案 2 :(得分:4)
泛型非常有用,我同意TObjectList<T>
(通常)比TCollection
更有用。但是类引用对于不同的场景更有用。它们实际上是不同范式的一部分。例如,当您拥有需要重写的虚方法时,类引用会很有用。虚方法覆盖必须与原始方法具有相同的签名,因此泛型范例不适用于此。
大量使用类引用的地方是表单流式传输。有时将DFM视为文本,您将看到每个对象都按名称和类引用。 (实际上名称是可选的。)当表单阅读器读取对象定义的第一行时,它将获取该类的名称。它在查找表中查找它并检索类引用,并使用该类引用来调用该类的TComponent.Create(AOwner: TComponent)
覆盖,以便它可以实例化正确类型的对象,然后它开始应用在DFM。这是类引用给你带来的东西,而且不能用泛型来完成。
答案 3 :(得分:1)
每当我需要能够创建一个不仅可以构造一个硬编码类的工厂,而且还可以构建任何继承自我的基类的类时,我也会使用元类。
然而,在Delphi圈子中,元类并不是我所熟悉的术语。我相信我们称之为类引用,它的名称不那么“神奇”,所以将两个常见的标记放在你的问题中是很棒的。
我见过这个地方的一个具体例子是JVCL JvDocking组件,其中“对接样式”组件向基础对接组件集提供元类信息,以便当用户拖动鼠标并停靠客户端时表单到停靠主机表单,“标签主机”和“联合主机”表单显示抓取条(外观与常规未停靠窗口的标题栏类似)可以是用户定义的插件类,它提供基于插件的自定义外观和自定义运行时功能。
答案 4 :(得分:0)
在我的一些应用程序中,我有一种机制,它将类连接到能够编辑一个或多个类的实例的表单。我有一个中央列表,其中存储了这些对:类引用和表单类引用。因此,当我有一个类的实例时,我可以查找相应的表单类,从中创建一个表单并让它编辑实例。
当然,这也可以通过让类方法返回适当的表单类来实现,但这需要类知道表单类。我的方法是一个更模块化的系统。表格必须知道班级,但不是相反。当您无法更改类时,这可能是一个关键点。