开发者

Class function that creates an instance of itself in Delphi

开发者 https://www.devze.com 2023-04-09 19:04 出处:网络
Can you have a class function that creates an instance of a class: TMyClass = class(TSomeParent) public

Can you have a class function that creates an instance of a class:

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

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

and then use it as follows

...   
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
...

And if so, what does that function code look like? Do you use TObject's NewInstance method and do you re-implement every-time for every derived class? Is it saver/better to use the Constructor?

The goal is to use this approach in开发者_如何学编程 a command pattern and load the command list with class types and a receiver e.g:

//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));

And the reason for this is that some commands will be specified via text/script and will need to be resolved at runtime.


What you're looking for is called the factory pattern. It can be done in Delphi; it's how the VCL deserializes forms, among other things. What you're missing is the registration/lookup part of the system. Here's the basic idea:

  • Somewhere, you set up a registration table. If you're on Delphi XE, you can implement this as a TDictionary<string, TMyClassType>, where TMyClassType is defined as class of TMyClass. This is important. You need a map between class names and class type references.
  • Put a virtual constructor on TMyClass. Everything that descends from it will use this constructor, or an override of it, when the factory pattern creates it.
  • When you create a new descendant class, have it call a method that will register itself with the registration table. This should happen at program startup, either in initialization or in a class constructor.

When you need to instantiate something from a script, do it like this:

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

You use the registration table to get the class reference from the class name, and call the virtual constructor on the class reference to get the right type of object out of it.


Yes, it is technically possible to create an instance from a class method, simply call the actual constructor and then return the instance it creates, eg:

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;

In which case, you are better off just using the constructor directly:

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;


Can you have a class function that creates an instance of a class.
Is it saver/better to use the Constructor?

Constructor is a class function that creates an instance of class. Just put:

constructor New(); virtual;

And you are good to go.

The virtual; part will let you call same New() constructor for all descendant classes.


Another option is to use RTTI. The code below runs as a normal method in my class as a way to get a new instance of the object with a subset of items, but as the items (along with the list object itself) are probably of descendent objects, creating an instance of the object in which the method is defined isn't good enough as it needs to be of the same type of the instance.

i.e.

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);

The implementation if GetSubRange would be something like...

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;

I appreciate for some things it may be a bit OTT, but in the design above, it works and is another alternative, although I appreciate, its not a class method.


You should use a constructor (a special "kind" of class function). TObject.NewInstance is not a suitable option, unless you require special memory allocation.

And regarding the Execute routine of the command list: the action involved now depends on the type of the object. Imagine a document being able to open, print, paste and save at the same time (not a weird assumption), that would be difficult to implement in this structure. Instead, consider to add interfaces (IOpenDocument, IPasteFromClipboard, IPrintable, ISaveDocument) which indeed could all be actions of one document instance.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号