Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class function that creates an instance of itself in Delphi

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.

like image 350
MX4399 Avatar asked Sep 28 '11 21:09

MX4399


2 Answers

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.

like image 146
Mason Wheeler Avatar answered Sep 30 '22 12:09

Mason Wheeler


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;
like image 31
Remy Lebeau Avatar answered Sep 30 '22 13:09

Remy Lebeau