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.
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>
, whereTMyClassType
is defined asclass 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.
精彩评论