首页 文章

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

提问于
浏览
7

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

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 回答

  • 1

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

    构造函数是一个创建类实例的类函数 . 刚刚放:

    constructor New(); virtual;
    

    你很高兴 .

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

  • 12

    您正在寻找的是工厂模式 . 它可以在Delphi中完成;它缺少's how the VCL deserializes forms, among other things. What you'是系统的注册/查找部分 . 这是基本的想法:

    • 在某处,您设置了一个注册表 . 如果您使用的是Delphi XE,则可以将其实现为 TDictionary<string, TMyClassType> ,其中 TMyClassType 定义为 class of TMyClass . 这个很重要 . 您需要类名和类类型引用之间的映射 .

    • 在TMyClass上放置一个虚拟构造函数 . 当工厂模式创建它时,从它下降的所有内容都将使用此构造函数或覆盖它 .

    • 创建新的后代类时,让它调用一个方法,该方法将自己注册到注册表 . 这应该在程序启动时发生,在 initialization 或类构造函数中 .

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

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

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

  • 2

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

    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;
    
  • 5

    另一种选择是使用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,但在上面的设计中,它起作用并且是另一种选择,虽然我很欣赏,但它不是一种类方法 .

  • 0

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

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

相关问题