在Delphi中创建自身实例的类函数

时间:2011-09-28 21:18:54

标签: delphi design-patterns delphi-xe

你能有一个创建类实例的类函数:

TMyClass = class(TSomeParent)
public
  class function New(AValue : integer) : TMyClass; 
end;

TDerivedClass = class(TMyClass)
public
  function Beep;
end;

然后按如下方式使用

...   
var
  myList : TList<T>;
  item : TDerivedClass;
begin
  myList.Add(TDerivedClass.New(1))
  myList.Add(TDerivedClass.New(3))
  myList.Add(TDerivedClass.New(5))

  for item in myList do
    item.Beep; //times the count in the class function
...

如果是这样,那个功能代码是什么样的?您是否使用TObject的NewInstance方法并且每次为每个派生类重新实现?使用构造函数是否更安全/更好?

目标是在命令模式中使用此方法并使用类类型和接收器加载命令列表,例如:

//FYI: document is an instance of TDocument
commandList.Execute(TOpenDocument(document)); 
commandList.Execute(TPasteFromClipboard(document)); 
//... lots of actions - some can undo
commandList.Execute(TPrintDocument(document)); 
commandList.Execute(TSaveDocument(document));

原因是某些命令将通过text / script指定,需要在运行时解析。

5 个答案:

答案 0 :(得分:12)

您正在寻找的是工厂模式。它可以在Delphi中完成;它是VCL如何反序列化形式等等。您缺少的是系统的注册/查找部分。这是基本的想法:

  • 在某处,您设置了注册表。如果您使用的是Delphi XE,则可以将其实现为TDictionary<string, TMyClassType>,其中TMyClassType定义为class of TMyClass。这个很重要。您需要在类名和类类型引用之间建立映射。
  • 在TMyClass上放置一个虚拟构造函数。当工厂模式创建时,从它下降的所有内容都将使用此构造函数或覆盖它。
  • 创建新的后代类时,让它调用一个将注册表注册的方法。这应该在程序启动时进行,无论是在初始化还是在类构造函数中。

当您需要从脚本中实例化某些内容时,请执行以下操作:

 class function TMyClass.New(clsname: string; [other params]): TMyClass;
 begin
   result := RegistrationTable[clsName].Create(other params);
 end;

使用注册表从类名中获取类引用,并在类引用上调用虚拟构造函数以从中获取正确类型的对象。

答案 1 :(得分:5)

是的,技术上可以从类方法创建实例,只需调用实际的构造函数然后返回它创建的实例,例如:

type
  TMyClass = class(TSomeParent)
  public
    constructor Create(AValue : Integer); virtual;
    class function New(AValue : integer) : TMyClass;
  end;

  TDerivedClass = class(TMyClass)
  public
    constructor Create(AValue : Integer); override;
    function Beep;
  end;

constructor TMyClass.Create(AValue : Integer);
begin
  inherited Create;
  ...
end;

function TMyClass.New(AValue : integer) : TMyClass;
begin
  Result := Create(AValue);
end;

constructor TDerivedClass.Create(AValue : Integer);
begin
  inherited Create(AValue);
  ...
end;

var
  myList : TList<TMyClass>;
  item : TMyClass;
begin
  myList.Add(TDerivedClass.New(1))
  myList.Add(TDerivedClass.New(3))
  myList.Add(TDerivedClass.New(5))
  for item in myList do
    TDerivedClass(item).Beep;

在这种情况下,最好直接使用构造函数:

type
  TMyClass = class(TSomeParent)
  end;

  TDerivedClass = class(TMyClass)
  public
    constructor Create(AValue : Integer);
    function Beep;
  end;

var
  myList : TList<TDerivedClass>;
  item : TDerivedClass;
begin
  myList.Add(TDerivedClass.Create(1))
  myList.Add(TDerivedClass.Create(3))
  myList.Add(TDerivedClass.Create(5))
  for item in myList do
    item.Beep;

答案 2 :(得分:2)

  

你能有一个创建类实例的类函数吗?
  使用构造函数是否更安全/更好?

构造函数是一个创建类实例的类函数。 只是说:

constructor New(); virtual;

你很高兴。

virtual;部分将允许您为所有后代类调用相同的New()构造函数。

答案 3 :(得分:1)

另一种选择是使用RTTI。下面的代码作为我的类中的常规方法运行,作为使用项子集获取对象的新实例的方法,但由于项(以及列表对象本身)可能是后代对象,因此创建实例定义方法的对象不够好,因为它需要与实例的类型相同。

TParentItem = Class
End;

TParentList = Class
  Items : TList<TParentItem>;
  Function GetSubRange(nStart,nEnd : Integer) : TParentList;
End;

TChildItem = Class(TParentItem)
end

TChildList = Class(TParentList)
end

List := TChildList.Create;
List.LoadData;

SubList := List.GetSubRange(1,3);

如果GetSubRange类似......

Function TParentList.GetSubRange(nStart,nEnd : Integer) : TParentList;
var
  aContext: TRttiContext;
  aType: TRttiType;
  aInsType : TRttiInstanceType;
  sDebug : String;
begin
  aContext := TRttiContext.Create;
  aType := aContext.GetType(self.ClassType);
  aInsType := aType.AsInstance;
  Result := aInsType.GetMethod('Create').Invoke(aInsType.MetaclassType,[]).AsType<TParentList>;
  sDebug := Result.ClassName; // Should be TChildList

  // Add the items from the list that make up the subrange.
End;

我很欣赏它可能有点OTT,但在上面的设计中,它有效并且是另一种选择,虽然我很欣赏,但它不是一种类方法。

答案 4 :(得分:0)

您应该使用构造函数(类函数的特殊“类型”)。 TObject.NewInstance不是合适的选项,除非您需要特殊的内存分配。

关于命令列表的执行例程:现在涉及的操作取决于对象的类型。想象一下,一个文档能够同时打开,打印,粘贴和保存(不是一个奇怪的假设),这在这个结构中很难实现。相反,请考虑添加接口(IOpenDocument,IPasteFromClipboard,IPrintable,ISaveDocument),这些接口确实可以是一个文档实例的操作。