Delphi类引用... aka metaclasses ...何时使用它们

Ken*_*ran 14 delphi metaclass class-reference

我已经阅读了官方文档,我理解了哪些类引用,但我没有看到它们何时以及为什么它们是替代品的最佳解决方案.

文档中引用的示例是TCollection,可以使用TCollectionItem的任何后代进行实例化.使用类引用的理由是它允许您在编译时调用类型未知的类方法(我假设这是TCollection的编译时).我只是没有看到使用TCollectionItemClass作为参数如何优于使用TCollectionItem.TCollection仍然能够保存TCollectionItem的任何后代,并且仍然能够调用TCollectionItem中声明的任何方法.不是吗?

将其与通用集合进行比较.TObjectList似乎提供与TCollection大致相同的功能,并具有类型安全性的额外好处.您无需继承TCollectionItem以存储对象类型,您可以根据需要将集合设置为特定类型.如果您需要从集合中访问项目的成员,则可以使用通用约束.除了在Delphi 2009之前程序员可以使用类引用这一事实之外还有其他令人信服的理由将它们用于通用容器吗?

文档中给出的另一个示例是将类引用传递给充当对象工厂的函数.在这种情况下,工厂用于创建TControl类型的对象.它不是很明显,但我假设TControl工厂正在调用传递给它的后代类型的构造函数而不是TControl的构造函数.如果是这种情况,那么我开始至少看到使用类引用的一些原因.

所以我想我真正想要了解的是什么时候和哪些类引用最合适,他们了什么开发人员?

Cos*_*und 22

MetaClasses和"类程序"

MetaClasses都是关于"类程序"的.从基础开始class:

type
  TAlgorithm = class
  public
    class procedure DoSomething;virtual;
  end;
Run Code Online (Sandbox Code Playgroud)

因为DoSomething是一个class procedure我们可以称之为无TAlgorithm的一个实例(它像任何其他全局过程).我们做得到:

TAlgorithm.DoSomething; // this is perfectly valid and doesn't require an instance of TAlgorithm
Run Code Online (Sandbox Code Playgroud)

一旦我们得到这个设置,我们可能会创建一些替代算法,所有算法都共享基本算法的一些位和和平.像这样:

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

我们现在有一个基类和两个后代类.以下代码完全有效,因为我们将方法声明为"类"方法:

TAlgorithm.DoSomething;
TAlgorithmA.DoSomething;
TAlgorithmB.DoSomething;
Run Code Online (Sandbox Code Playgroud)

我们来介绍一下这种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;
Run Code Online (Sandbox Code Playgroud)

TAlgorithmClass是一种可以像任何其他数据类型一样使用的数据类型,它可以存储在变量中,作为参数传递给函数.换句话说,我们可以这样做:

procedure CrunchSomeData(Algo:TAlgorithmClass);
begin
  Algo.DoSomething;
end;

CrunchSomeData(TAlgorithmA);
Run Code Online (Sandbox Code Playgroud)

在这个例子中,程序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;
Run Code Online (Sandbox Code Playgroud)

虚拟构造器

Delphi构造函数就像"类函数"一样工作; 如果我们有一个元类,并且元类知道虚拟构造函数,我们就能够创建后代类型的实例.当您点击"添加"按钮时,TCollection的IDE编辑器会使用它来创建新项目.所有TCollection需要让这个工作是一个TCollectionItem的MetaClass.

  • 我很好奇为什么有人会否决这个答案。Cosmin显然投入了很多心思和精力。当然,除非它明显错误或具有误导性(事实似乎并非如此),否则否决票是没有根据的。 (2认同)

Mar*_*ema 8

是的,Collection仍然可以保存TCollectionItem的任何后代并在其上调用方法.但是,它无法实例化TCollectionItem的任何后代的新实例.调用TCollectionItem.Create构造TCollectionItem的实例,而

private
  FItemClass: TCollectionItemClass;
...

function AddItem: TCollectionItem;
begin
  Result := FItemClass.Create;
end;
Run Code Online (Sandbox Code Playgroud)

将构造一个TCollectionItem后代类在FItemClass中保存的实例.

我没有做太多的通用容器,但我认为,如果有选择,我会选择通用容器.但是在任何一种情况下,如果我想让列表实例化并且在容器中添加项目时需要做的其他事情,我仍然必须使用元类,并且我事先不知道确切的类.

例如,可观察的TObjectList后代(或通用容器)可能具有以下内容:

function AddItem(aClass: TItemClass): TItem;
begin
  Result := Add(aClass.Create);
  FObservers.Notify(Result, cnNew);
  ...
end;
Run Code Online (Sandbox Code Playgroud)

我想简而言之,元类的优点/好处是任何只有知识的方法/类

type
  TMyThing = class(TObject)
  end;
  TMyThingClass = class of TMyThing;
Run Code Online (Sandbox Code Playgroud)

能够构建任何TMyThing后代的实例,无论它们是否已被声明.


Mas*_*ler 5

泛型非常有用,我同意它TObjectList<T>(通常)比TCollection. 但类引用对于不同的场景更有用。它们确实是不同范式的一部分。例如,当您有需要重写的虚拟方法时,类引用可能会很有用。虚拟方法重写必须具有与原始方法相同的签名,因此泛型范例在这里不适用。

大量使用类引用的地方之一是表单流。有时将 DFM 作为文本查看,您会发现每个对象都是通过名称和类引用的。(实际上,名称是可选的。)当表单读取器读取对​​象定义的第一行时,它会获取类的名称。它在查找表中查找并检索类引用,并使用该类引用来调用该类的重写,TComponent.Create(AOwner: TComponent)以便它可以实例化正确类型的对象,然后开始应用 DFM 中描述的属性。这是类引用给你带来的东西,而它不能用泛型来完成。