Consider an interface like
IMyInterface = interface
procedure DoSomethingRelevant;
procedure Load (Stream : TStream);
procedure Save (Stream : TStream);
end;
and several classes that implement the interface:
TImplementingClass1 = class (TInterfacedObject, IMyInterface)
...
end;
TImplementingClass2 = class (TInterfacedObject, IMyInterface)
...
end;
...
I have a class that has a list of IMyInterface implementors:
TMainClass = class
strict private
FItems : TList <IMyInterface>;
public
procedure LoadFromFile (const FileName : String);
procedure SaveToFile (const FileName : String);
end;
Now to the question: how can I load the main class and especially the item list in an object-oriented manner? Before I can call the virtual Load method for the items, I have to create them and thus have to know their type. In my current implementation I store the number of items and then for each item
a type identifier (IMyInterface gets an additional GetID function)
call the Save method of the item
But that means that during loading I have to do something like
ID := Reader.ReadInteger;
case ID of
itClass1 : Item := TImplementingClass1.Create;
itClass2 : Item := TImplementingClass2.Create;
...
end;
Item.Load (Stream);
But that doesn't seem to be very object-oriented since I have to fiddle with existing code every time I add a new implementor. Is there a better way to handle this situation?
One solution would be to implement a factory where all classes register themselve with a unique ID.
TCustomClassFactory = class(TObject)
public
procedure Register(AClass: TClass; ID: Integer);
function Create(const ID: Integer): IMyInterface;
end;
TProductionClassFactory = class(TCustomClassFactory)
public
constructor Create; override;
end;
TTestcase1ClassFactory = class(TCustomClassFactory);
public
constructor Create; override;
end;
var
//***** Set to TProductionClassFactory for you production code,
// TTestcaseXFactory for testcases or pass a factory to your loader object.
GlobalClassFactory: TCustomClassFactory;
implementation
constructor TProductionClassFactory.Create;
begin
inherited Create;
Register(TMyImplementingClass1, 1);
Register(TMyImplementingClass2, 2);
end;
constructor TTestcase1ClassFactory.Create;
begin
inherited Create;
Register(TMyImplementingClass1, 1);
Register(TDoesNotImplementIMyInterface, 2);
Register(TDuplicateID, 1);
Register(TGap, 4);
...
end;
Advantages
You can remove the conditional logic from your current load method.
One place to check for duplicate or missing ID's.
You need a class registry, where you store every class reference together with their unique ID. The classes register themselves in the initialization section of their unit.
TImplementingClass1 = class (TInterfacedObject, IMyInterface)
...
end;
TImplementingClass2 = class (TInterfacedObject, IMyInterface)
...
end;
TMainClass = class
public
procedure LoadFromFile (const FileName : String);
procedure SaveToFile (const FileName : String);
end;
Edit: moved the class registry into a separate class:
TMyInterfaceContainer = class
strict private
class var
FItems : TList <IMyInterface>;
FIDs: TList<Integer>;
public
class procedure RegisterClass(TClass, Integer);
class function GetMyInterface(ID: Integer): IMyInterface;
end;
procedure TMainClass.LoadFromFile (const FileName : String);
...
ID := Reader.ReadInteger;
// case ID of
// itClass1 : Item := TImplementingClass1.Create;
// itClass2 : Item := TImplementingClass2.Create;
// ...
// end;
Item := TMyInterfaceContainer.GetMyInterface(ID);
Item.Load (Stream);
...
initialization
TMyInterfaceContainer.RegisterClass(TImplementingClass1, itClass1);
TMyInterfaceContainer.RegisterClass(TImplementingClass2, itClass2);
This should point you into the direction, for a very good introduction into these methods read the famous Martin Fowler article, esp. the section about Interface Injection
Related
There are two objects: TFoo, TFoo2.
There is also a class reference : TFooClass = class of TFoo;
Both are descendants from TPersistent.
They have their own constructors:
type
TFoo = class(TPersistent)
private
FC:Char;
public
constructor Create; virtual;
published
property C:Char read FC write FC;
end;
TFoo2 = class(TFoo)
public
constructor Create; override;
end;
TFooClass = class of TFoo;
...
constructor TFoo.Create;
begin
inherited Create;
C :=' 1';
end;
constructor TFoo2.Create;
begin
inherited Create;
C := '2';
end;
I want to create a TFoo2 object from a string, which is actually its class name : 'TFoo2'
Here is the procedure, which works fine:
procedure Conjure(AClassName:string);
var
PClass : TPersistentClass;
p :TPersistent;
begin
PClass := TPersistentClass(FindClass(AClassName))
p := TFooClass(PClass).Create; // <-- here is called appropriate constructor
end;
Now, I want to have similar objects like: TBobodo, TBobodo2.
And a class reference of course : TBobodoClass = class of TBobodo;
And so on...
Now, how can I pass a class reference as a parameter into a procedure, in order to secure the right constructor is called?
procedure Conjure(AClassName:string; ACLSREF: ???? ); // <-- something like that
var
PClass : TPersistentClass;
p :TPersistent;
begin
PClass := TPersistentClass(FindClass(AClassName))
p := ACLSREF(PClass).Create; // <-- something like that
end;
Is it possible?
There is no way to do what you want in Delphi 7. The metaclass reference has to be explicit at compile-time at the call site, not handled at runtime.
In Delphi 2009 and later, you may 1 be able to do something with Generics, eg:
1: I have not tried this myself yet.
type
TConjureHelper = class
public
class procedure Conjure<TClassType>(const AClassName: string);
end;
class procedure TConjureHelper.Conjure<TClassType>(const AClassName: string);
var
PClass : TPersistentClass;
p : TPersistent;
begin
PClass := TPersistentClass(FindClass(AClassName));
p := TClassType(PClass).Create;
...
end;
...
TConjureHelper.Conjure<TFooClass>('TFoo2');
TConjureHelper.Conjure<TBobodoClass>('TBobodo2');
...
But Delphi 7 certainly does not support Generics.
I had the same problem and after some struggles, I found a quite simple solution: metaclass is invented exactly for this purpose!
In your case, you can pass the metaclass as parameter and use it directly without the cumbersome finding class and type casting.
type
TFooClass = class of TFoo;
procedure Conjure(aFooClass : TFooClass); // <-- something like that
var
p :TPersistent;
begin
p := aFooClass.Create; // it will work!
end;
and by calling, you simply use:
Conjure(TFoo); // <- for Foo class or
Conjure(TFoo2); // <- for Foo2 class and so on
I want to define three base classes, TMyBaseClass that keeps data, TMyBaseClassList that holds a list of instances of TMyBaseClass, and TMyBaseClassReader that scrolls through a dataset and fills a TMyBaseClassList object. This is my code:
TMyBaseClass = class
public
// properties
constructor Create;
end;
TMyBaseClassList<T: TMyBaseClass, constructor> = class(TObjectList<TMyBaseClass>)
public
function AddNew: T;
end;
TMyBaseClassReader<T: TMyBaseClass> = class
public
class procedure ReadProperties(const DataSet: TCustomADODataSet;
const Item: T); virtual; abstract;
class procedure ReadDataSet(const DataSet: TCustomADODataSet;
const List: TMyBaseClassList<T>);// <- E2513
end;
...
constructor TMyBaseClass.Create;
begin
inherited;
end;
function TMyBaseClassList<T>.AddNew: T;
begin
Result := T.Create;
Add(Result);
end;
class procedure TMyBaseClassReader<T>.ReadDataSet;
var
NewItem: T;
begin
while not DataSet.Eof do
begin
NewItem := List.AddNew;
ReadProperties(DataSet, NewItem);
DataSet.Next;
end;
end;
Then I want to derive child classes and only implement ReadProperties method. But I'm getting an E2513 error:
E2513 Type parameter 'T' must have one public parameterless constructor named Create
What is the problem and how can I fix it?
The error means that the compiler cannot be sure that T meets the requirements. Declare the derived class like so
TMyBaseClassReader<T: TMyBaseClass, constructor>
I work with Delphi 2006 and I have a complex class named TMyClassTest that have many methods
Some of those methods create nonvisual components and assign event handlers of those components and run methods of those components.
Also I have two classes that implement the same interface like below:
TMyClass1 = class(Class1, Interface1)
... //procedures from the Interface1
procedure MyClass1Proc1;
end;
TMyClass2 = class(Class2, Interface1)
... //procedures from the Interface1
procedure MyClass2Proc1;
procedure MyClass2Proc2
end;
Now I need that TMyClass1 and TMyClass2 to 'inherit' the TMyClassTest, too.
Much more ... Interface1 must contain (beyond its methods) all the methods from the MyClassTest.
How can I avoid to implement (like copy/paste) on both clases (TMyClass1 and TMyClass2) all the procedures from TMyClassTest ?
I don't want to keep the same code on three separate places.
Based on Arioch's comments I created a solution like:
(see http://docwiki.embarcadero.com/RADStudio/XE3/en/Implementing_Interfaces#Implementing_Interfaces_by_Delegation_.28Win32_only.29)
type
IMyInterface = interface
procedure P1;
procedure P2;
end;
TMyImplClass = class
procedure P1;
procedure P2;
end;
TMyClass1 = class(Class1, IMyInterface)
FMyImplClass: TMyImplClass;
property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
procedure IMyInterface.P1 = MyP1;
procedure MyP1;
end;
TMyClass2 = class(TInterfacedObject, IMyInterface)
FMyImplClass: TMyImplClass;
property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
procedure P3;
procedure P4;
end;
procedure TMyImplClass.P1;
// ...
procedure TMyImplClass.P2;
// ...
procedure TMyClass1.MyP1;
// ...
procedure TMyClass2.P3;
// ...
procedure TMyClass2.P4;
// ...
var
MyClass: TMyClass1;
MyInterface: IMyInterface;
begin
MyClass := TMyClass1.Create;
MyClass.FMyImplClass := TMyImplClass.Create; //Error !!!! FMyImplClass is a read only property !!!
MyInterface := MyClass;
MyInterface.P1; // calls TMyClass1.MyP1;
MyInterface.P2; // calls TImplClass.P2;
end;
Because I have an error at MyClass.FMyImplClass := TMyImplClass.Create; I tried to create FMyImplClass declaring constructor from TMyClass1 and TMyClass2 but don't work ok.
Is there some other method to create FMyImplClass ?
Now I tried a solution that seem to work ok. Can there happen some hidden efects?
type
IMyInterface = interface
procedure P1;
procedure P2;
procedure CreateFMyImplClass;
end;
TMyImplClass = class
procedure P1;
procedure P2;
end;
TMyClass1 = class(Class1, IMyInterface)
FMyImplClass: TMyImplClass;
property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
procedure IMyInterface.P1 = MyP1;
procedure MyP1;
procedure CreateFMyImplClass;
end;
TMyClass2 = class(TInterfacedObject, IMyInterface)
FMyImplClass: TMyImplClass;
property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
procedure P3;
procedure P4;
procedure CreateFMyImplClass;
end;
procedure TMyImplClass.P1;
// ...
procedure TMyImplClass.P2;
// ...
procedure TMyClass1.MyP1;
// ...
procedure TMyClass1.CreateFMyImplClass;
begin
FMyImplClass := TMyImplClass.Create;
end;
procedure TMyClass2.P3;
// ...
procedure TMyClass2.P4;
// ...
procedure TMyClass2.CreateFMyImplClass;
begin
FMyImplClass := TMyImplClass.Create;
end;
var
MyInterface: IMyInterface;
begin
if WantRemote then
MyInterface := TMyClass1.Create
else
MyInterface := TMyClass2.Create;
MyInterface.CreateFMyImplClass; // create FMyImplClass ;
MyInterface.P2; // calls TImplClass.P2;
end;
Delphi does not have Scala-like traits or Python-like mixins, nor it support multiple inheritance a la C++.
If you cannot make Class1 and Class2 inherit from TMyClassTest, then perhaps you have to rely on interface delegation: make TMyClassX no more implementing Interface1 directly, but instead add them a field of TMyClassTest and delegate their Interface1 to this field.
I think you'd better
move those new common functions into some Interface0 type
make Interface1 inherited from Interface0
make some TMyClassesBaseCommonTrait class, implementing Interface0
make two subclasses TMyClass1InternalEngine(TMyClassesBaseCommonTrait) and TMyClass2InternalEngine(TMyClassesBaseCommonTrait) implementing (in different, TMyClassX-specific ways, the rest of Interface1(Interface0) API
have TMyClassX classes internal private field of TMyClass2InternalEngine type doign real implemntation
Google for "delphi interface delegation" shows this as top link: Delphi: How delegate interface implementation to child object?
I have CObject as main class and CRock, CDesk, CComputer as derivates from CObject. I would like to write a function that reads a class enumeration (integer probably like OBJECT_COMPUTER) and returns the specific type.
Example:
function createObject( iType : Integer ) : CObject;
begin
case iType of
OBJECT_ROCK : Result := CRock.Create();
OBJECT_DESK : Result := CDesk.Create();
end;
end;
so I can use it like this: myRock := createObject( OBJECT_ROCK );
Now my problem is that the object returned is the main class parent and I can't use Rock functions on 'myRock' without type casting 'createObject( OBJECT_ROCK )' from CObject to CRock and I don't want to have 3 functions for each sub-class. Any ideas? Thanks in advance.
If I understood correct, you'd declare a skeleton of derived functionality on the base class with abstract methods, then override and implement the method in each derived class.
type
CObject = class
procedure DoIt; virtual; abstract;
end;
CRock = class(CObject)
procedure DoIt; override;
end;
CDesk = class(CObject)
procedure DoIt; override;
end;
var
myRock: CObject;
begin
myRock := createObject(OBJECT_ROCK);
myRock.DoIt;
myRock.Free;
end;
In the above example, 'DoIt' call on the 'myRock' instance would be correctly resolved to the method of that class.
If this is relevant at all read about abstract methods here.
Like the previous example, but rather like this. We call it Inheritance, Polymorphism.
type
TcObject = class
procedure DoIt; virtual; abstract;
end;
TcRock = class(CObject)
procedure DoIt; override;
end;
TcDesk = class(CObject)
procedure DoIt; override;
end;
var
myRock: TcObject;
begin
myRock := TcRock.Create; //Inherits from TcObject and instantiate TcRock class.
myRock.DoIt; //Will automaticall call TcRock.Doit --Polymorphism
myRock.Free;
end;
I've got a little class hierarchy where each class corresponds to a certain TComponent descendent (say base class TDefaultFrobber with descendents TActionFrobber and TMenuItemFrobber, corresponding to TComponent, TCustomAction and TMenuItem, respectively). Now I want a factory (?) function something like this:
function CreateFrobber(AComponent: TComponent): IFrobber;
begin
if AComponent is TCustomAction then
Result := TActionFrobber.Create(TCustomAction(AComponent))
else if AComponent is TMenuItem then
Result := TMenuItemFrobber.Create(TMenuItem(AComponent))
else
Result := TDefaultFrobber.Create(AComponent);
end;
Can I somehow refactor this to use virtual functions or something similar instead of the if-else cascade or RTTI?
Edit: My solution for now:
unit Frobbers;
interface
uses
Classes;
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass; AFrobberClass: TComponentFrobberClass); static;
end;
implementation
uses
ActnList,
Menus;
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
constructor TComponentFrobber.Create(AComponent: TComponent);
begin
inherited Create;
FComponent := AComponent;
end;
class function TComponentFrobber.FindFrobberClass(AComponent: TComponent): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if AComponent is FComponentFrobberRegistry[i].ComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
class procedure TComponentFrobber.RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass);
var
i: Integer;
begin
Assert(FindFrobberClass(AComponentClass) = nil, 'Duplicate Frobber class');
i := Length(FComponentFrobberRegistry);
SetLength(FComponentFrobberRegistry, Succ(i));
FComponentFrobberRegistry[i].ComponentClass := AComponentClass;
FComponentFrobberRegistry[i].FrobberClass := AFrobberClass;
end;
function CreateComponentFrobber(AComponent: TComponent): IComponentFrobber;
var
FrobberClass: TComponentFrobberClass;
begin
FrobberClass := TComponentFrobber.FindFrobberClass(AComponent);
Assert(FrobberClass <> nil);
Result := FrobberClass.Create(AComponent);
end;
type
TActionFrobber = class(TComponentFrobber);
TMenuItemFrobber = class(TComponentFrobber);
initialization
TComponentFrobber.RegisterFrobber(TCustomAction, TActionFrobber);
TComponentFrobber.RegisterFrobber(TMenuItem, TMenuItemFrobber);
end.
Thanks to Cesar, Gamecat and mghie.
If you create a class with a virtual constructor and create a class type for that class. You can create a lookuplist based on the component class name.
Example:
type
TFrobber = class
public
constructor Create; virtual;
class function CreateFrobber(const AComponent: TComponent): TFrobber;
end;
TFrobberClass = class of TFrobber;
type
TFrobberRec = record
ClassName: ShortString;
ClassType: TFrobberClass;
end;
const
cFrobberCount = 3;
cFrobberList : array[1..cFrobberCount] of TFrobberRec = (
(ClassName : 'TAction'; ClassType: TActionFrobber),
(ClassName : 'TButton'; ClassType: TButtonFrobber),
(ClassName : 'TMenuItem'; ClassType: TMenuItemFrobber)
);
class function TFrobber.CreateFrobber(const AComponent: TComponent): TFrobber;
var
i : Integer;
begin
Result := nil;
for i := 1 to cFrobberCount do begin
if AComponent.ClassName = cFrobberList[i].ClassName then begin
Result := cFrobberList[i].ClassType.Create();
Exit;
end;
end;
end;
You can of course also work with a dynamic list (dictionary) but then you must register each combination somehow.
Update
To commnent on the remarks of mghie.
You are perfectly right. But this is not possibly without really ugly tricks.
Right now you have to use the initialization/finalization sections of a unit to regoister a class. But it would be cool to add a initialization/finalization class method to a class. These have to be called along with the initialization (and finalization) of the unit. Like this:
class
TFrobber = class
private
initialization Init; // Called at program start just after unit initialization
finalization Exit; // called at program end just before unit finalization.
end;
2 suggestions:
Make class pair array of classes, then you can get the Index and use the pair of the class constructor,
var
ArrayItem: array[0..1] of TComponentClass = (TActionFrobber, TMenuItemFrobber);
ArrayOwner: array[0..1] of TComponentClass = (TCustomAction, TMenuItem);
function CreateFrobber(AComponent: TComponentClass): IFrobber;
var
Index: Integer;
begin
Result:= nil;
for I := Low(ArrayOwner) to High(ArrayOwner) do
if AComponent is ArrayOwner[I] then
begin
Result:= ArrayItem[I].Create(AComponent);
Break;
end;
if Result = nil then
Result:= TDefaultFrobber.Create(AComponent);
end;
or use RTTI + ClassName conventions, like this:
function CreateFrobber(AComponent: TComponentClass): IFrobber;
const
FrobberClassSuffix = 'Frobber';
var
LClass: TComponentClass;
LComponent: TComponent;
begin
LClass:= Classes.FindClass(AComponent.ClassName + FrobberClassSuffix);
if LClass <> nil then
LComponent:= LClass.Create(AComponent)
else
LComponent:= TDefaultFrobber.Create(AComponent);
if not Supports(LComponent, IFrobber, Result) then
Result:= nil;
end;
I'd like to add some comments to your current solution, answering here as this can not really be done in the comments section:
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent):
TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass); static;
end;
There is not much point in using TInterfacedObject for the base class, as you will always need the object, not the interface it implements - how else would you find your concrete Frobber class? I would split this into TComponentFrobber, descending from TInterfacedObject, and a TComponentRegistry class (descending from TObject) that has the class methods. You can then of course make the registry class more generic, it is not tied to TComponentFrobber and could be reused.
Edit: I have used similar class registries for example when loading files: load the identifier for the next object (could be for example string, integer or GUID), then get the correct class to instantiate from the registry, then create and load the object.
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
This is OK if you will never add or remove classes to / from the registry, but generally I would not use an array but a list for the registry entries.
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
Searching backwards in the array will not help for finding the most specialized frobber, unless you add them in the correct order (least specialized first). Why don't you check for the ClassType being equal? There is also ClassParent to traverse the class hierarchy, if you need to test for base classes too.