i want typecast using with class functions.
i have base (TBase),derived (TDer) and typecasting (TMyType) class.
Ver : Delphi 7
TBase = class;
TDer = class;
TMyType = class;
TBase = class
function Say : String;
class function MYType:TMyType;
end;
TDer = class(TBase)
a: string;
b: string;
function Say2 : String;
end;
TMyType=class(TBase)
class function AsDer:TDer;
end;
{ TBase }
class function TBase.MYType: TMyType;
begin
Result:=TMyType(Self);
end;
function TBase.Say: String;
begin
Result:='TBase';
end;
{ TDer }
function TDer.Say2: String;
begin
Result:='TDer';
end;
{ TMyType }
class function TMyType.AsDer: TDer;
begin
Assert(Assigned(Self));
Result := TDer(Self) ;
end;
Sample usage is below, it's calls method but when set/get field's raise error.
procedure TForm1.Button1Click(Sender: TObject);
var
b,c:TBase;
begin
b:=TDer.Create;
c:=b.MYType.AsDer;
ShowMessage(b.MYType.AsDer.Say2); // OK. Running
if (#b<>#c) then ShowMessage('Not Equal'); // Shows message, Why ?
b.MYType.AsDer.a:='hey'; // Error
FreeAndNil(b);
end;
Do you have any idea?
The fundamental problem is here:
class function TBase.MYType: TMyType;
begin
Result:=TMyType(Self);
end;
This is a class method and so Self refers to a class and not an instance. Casting it to be an instance does not make it so. Exactly the same error is made in your AsDer class function.
Looking into the specifics, the call to
b.MYType.AsDer.Say2
is benign and appears to work fine because it does not refer to Self. You could equally write TDer(nil).Say2 and that code would also work without problem. Now, if the function Say2 referred to Self, that is referred to an instance, then there would be a runtime error.
#b<>#c
always evaluates to true because you are comparing the locations of two distinct local variables.
b.MYType.AsDer.a
is a runtime error because AsDer does not return an instance of TDer. So when you attempt to write to a you have a runtime error. This is because you are referring to Self and that's why this code fails, but the earlier call to Say2 does not.
I'm not really sure what you are trying to do here, but it looks all wrong. Even if you were working with instance methods rather than class methods, it would simply be wrong to case a base class instance to a derived class instance. If something is the wrong type, no amount of casting will turn it into the right type.
Furthermore, you should never write code that has a method of TBase assuming it is of type TDerived. The base class should know absolutely nothing of its derived classes. That is one of the very basic tenets of OOP design.
Here is the edited the new version :
TBase = class;
TDer = class;
TMyType = class;
TBase = class
MYType:TMyType;
constructor Create;
destructor Destroy;
function Say : String;
end;
TDer = class(TBase)
a: string;
b: string;
function Say2 : String;
end;
TMyType=class
public
T: TObject;
function AsDer:TDer;
end;
{ TBase }
constructor TBase.Create;
begin
MYType:=TMYType.Create;
MYType.T:=TObject(Self);
end;
destructor TBase.Destroy;
begin
MYType.Free;
end;
function TBase.Say: String;
begin
Result:='TBase';
end;
{ TDer }
function TDer.Say2: String;
begin
Result:='TDer';
end;
{ TMyType }
function TMyType.AsDer: TDer;
begin
Result := TDer(T) ;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
b:TBase;
c:TDer;
begin
b:=TDer.Create;
TDer(b).a:='a';
c:=b.MYType.AsDer;
ShowMessage('b.MYType.AsDer='+b.MYType.AsDer.a+', c.a ='+ c.a); // OK. Running
FreeAndNil(b);
end;
Related
To my thinking, the code below should fail to compile because the method TSubB.DoSomething is protected, thus not visible from TSubA.DoSomething. (They are siblings, not parent/child.) In fact it compiles and when you run it, it actually calls TBase.DoSomething. (I got burned by this because I'd forgotten DoSomething was protected.)
Now it gets weird. If I paste the code from uBase.pas into Project1.dpr and remove uBase.pas from the project, I do indeed get a compiler error on that line.
Can anyone explain what's going on?
(Sorry to paste so much code. This really does seem to be the minimal test case.)
Project1.dpr
program Project1;
{$APPTYPE CONSOLE}
uses
uBase in 'uBase.pas',
uSubB in 'uSubB.pas';
var
obj : TBase;
begin
obj := TSubA.Create;
Writeln(obj.Something);
obj.Free;
end.
uBase.pas
unit uBase;
interface
type
TBase = class (TObject)
protected
class function DoSomething : string; virtual;
public
function Something : string;
end;
TSubA = class (TBase)
protected
class function DoSomething : string; override;
end;
implementation
uses
uSubB;
function TBase.Something : string;
begin
Result := DoSomething;
end;
class function TBase.DoSomething : string;
begin
Result := 'TBase'; // Override in subclass.
end;
class function TSubA.DoSomething : string;
begin
Result := 'Same as ' + TSubB.DoSomething; // Expect compiler error here
end;
end.
uSubB.pas
unit uSubB;
interface
uses
uBase;
type
TSubB = class (TBase)
protected
class function DoSomething : string; override;
end;
implementation
class function TSubB.DoSomething : string;
begin
Result := 'TSubB';
end;
end.
Edit
If you move all the code from uBase.pas into Project1.dpr and remove uBase.pas from the project, then the compiler no longer accepts the call to TSubB.DoSomething. I'm not sure why this is any different in terms of visibility to the compiler.
Revised Project1.dpr
program Project1;
{$APPTYPE CONSOLE}
uses
// uBase in 'uBase.pas',
uSubB in 'uSubB.pas';
type
TBase = class (TObject)
protected
class function DoSomething : string; virtual;
public
function Something : string;
end;
TSubA = class (TBase)
protected
class function DoSomething : string; override;
end;
function TBase.Something : string;
begin
Result := DoSomething;
end;
class function TBase.DoSomething : string;
begin
Result := 'TBase'; // Override in subclass.
end;
class function TSubA.DoSomething : string;
begin
Result := 'Same as ' + TSubB.DoSomething; // Actual compiler error
end;
var
obj : TBase;
begin
obj := TSubA.Create;
Writeln(obj.Something);
obj.Free;
end.
TSubB.DoSomething is indeed not visible. But the compiler looks in all ancestor classes for DoSomething. The first one it finds is TBase.DoSomething which is visible. Hence the program compiles.
As to your edit, that changes everything. After your edit you have two different TBase classes. The one defined in the dpr file, and the one defined in the uBase unit. That's you intend. And there's really no point defining base classes in a dpr file because units cannot use dpr files.
I'm trying to extend a protected virtual method of TObjectDispatch. But this method never gets called.
[edited to reproduce the problem].
When I override GetPropInfo and use it in TMyDispatch it works as expected. The overrided method is called. However the overrided method on TMyDispatchItem when created by TMyDispatch (to simulate my real world example) is not called.
{$METHODINFO ON}
TExtDispatch = class(TObjectDispatch)
protected
function GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo; override;
public
constructor Create;
end;
TMyDispatchItem = class(TExtDispatch)
private
FItemValue: string;
public
procedure ShowItemValue;
published
property ItemValue: string read FItemValue write FItemValue;
end;
TMyDispatch = class(TExtDispatch)
public
function GetItem: TMyDispatchItem;
private
FValue: string;
public
procedure ShowValue;
published
property Value: string read FValue write FValue;
end;
{$METHODINFO OFF}
TTestForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
TestForm: TTestForm;
implementation
{$R *.dfm}
procedure TTestForm.Button1Click(Sender: TObject);
var
V: Variant;
VI: Variant;
begin
V := IDispatch(TMyDispatch.Create);
V.Value := 100; //this calls inherited getpropinfo
V.ShowValue;
VI := V.GetItem;
VI.ItemValue := 5; //this doesn't
VI.ShowItemValue;
end;
{ TExtDispatch }
constructor TExtDispatch.Create;
begin
inherited Create(Self, False);
end;
function TExtDispatch.GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo;
begin
Result := inherited GetPropInfo(AName, AInstance, CompIndex);
ShowMessage('GetPropInfo: ' + AName);
end;
{ TMyDispatch }
function TMyDispatch.GetItem: TMyDispatchItem;
begin
Result := TMyDispatchItem.Create;
end;
procedure TMyDispatch.ShowValue;
begin
ShowMessage('My dispatch: ' + Value);
end;
{ TMyDispatchItem }
procedure TMyDispatchItem.ShowItemValue;
begin
ShowMessage('My item value: ' + FItemValue);
end;
end.
I've actually found a way to overcome this problem by changing the datatype of TMyDispatch.GetItem to return as a Variant instead. Like this:
function TMyDispatch.GetItem: Variant;
begin
Result := IDispatch(TMyDispatchItem.Create);
end;
And now suddenly the overrided method is called. I really would like to understand what's going on here.
Any more ideas or explainations?
Virtual method dispatch in Delphi is known to work. So, if TExtDispatch.GetPropInfo is not being executed then these are the possible reasons:
The GetPropInfo method is not being called at all.
The actual instance on which GetPropInfo is being called is not an instance of TExtDispatch.
If you showed the rest of the code then we could be more sure, but the above options should be enough for you to work it out.
The only place that calls GetPropInfo is GetIDsOfNames. If your overridden GetIDsOfNames doesn't call GetPropInfo then nothing else will.
Considering your updated code, I ran it under the debugger. When the button is clicked, TObjectDispatch.GetPropInfo is called twice. The first time it is called as a result of the call to inherited GetPropInfo() in TExtDispatch.GetPropInfo. The second time it is called you can inspect ClassName to find out what class Self is. When you do that you will find that ClassName evaluates to 'TObjectDispatch'. In which case, item 2 from my list is the explanation.
I don't really understand what you are trying to do here. However, I suspect that your problem stems from the way GetItem is implemented. I suspect it should be like this:
function TMyDispatch.GetItem: IDispatch;
begin
Result := TMyDispatchItem.Create;
end;
There should have been alarm bells going off when you assigned the return value of a TInterfacedObject constructor to an object reference. That's always an error. You must assign that to an interface reference.
I expect that what happens is that the dispatch code will use an IDispatch if it encounters one, but if it finds an instance of a class instead it creates a new IDispatch to do the work. And that's the third instance of TObjectDispatch.
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;
Using Delphi 2010, let's say I've got a class declared like this:
TMyList = TList<TMyObject>
For this list Delphi kindly provides us with an enumerator, so we can write this:
var L:TMyList;
E:TMyObject;
begin
for E in L do ;
end;
The trouble is, I'd like to write this:
var L:TMyList;
E:TMyObject;
begin
for E in L.GetEnumerator('123') do ;
end;
That is, I want the ability to provide multiple enumerators for the same list, using some criteria. Unfortunately the implementation of for X in Z requires the presence of a function Z.GetEnumerator, with no parameters, that returns the given enumerator! To circumvent this problem I'm defining an interface that implements the "GetEnumerator" function, then I implement a class that implements the interface and finally I write a function on TMyList that returns the interface! And I'm returning an interface because I don't want to be bothered with manually freeing the very simple class... Any way, this requires a LOT of typing. Here's how this would look like:
TMyList = class(TList<TMyObject>)
protected
// Simple enumerator; Gets access to the "root" list
TSimpleEnumerator = class
protected
public
constructor Create(aList:TList<TMyObject>; FilterValue:Integer);
function MoveNext:Boolean; // This is where filtering happens
property Current:TTipElement;
end;
// Interface that will create the TSimpleEnumerator. Want this
// to be an interface so it will free itself.
ISimpleEnumeratorFactory = interface
function GetEnumerator:TSimpleEnumerator;
end;
// Class that implements the ISimpleEnumeratorFactory
TSimpleEnumeratorFactory = class(TInterfacedObject, ISimpleEnumeratorFactory)
function GetEnumerator:TSimpleEnumerator;
end;
public
function FilteredEnum(X:Integer):ISimpleEnumeratorFactory;
end;
Using this I can finally write:
var L:TMyList;
E:TMyObject;
begin
for E in L.FilteredEnum(7) do ;
end;
Do you know a better way of doing this? Maybe Delphi does support a way of calling GetEnumerator with a parameter directly?
Later Edit:
I decided to use Robert Love's idea of implementing the enumerator using anonymous methods and using gabr's "record" factory to save yet an other class. This allows me to create a brand new enumerator, complete with code, using just a few lines of code in a function, no new class declaration required.
Here's how my generic enumerator is declared, in a library unit:
TEnumGenericMoveNext<T> = reference to function: Boolean;
TEnumGenericCurrent<T> = reference to function: T;
TEnumGenericAnonim<T> = class
protected
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
function GetCurrent:T;
public
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
TGenericAnonEnumFactory<T> = record
public
FEnumGenericMoveNext:TEnumGenericMoveNext<T>;
FEnumGenericCurrent:TEnumGenericCurrent<T>;
constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>);
function GetEnumerator:TEnumGenericAnonim<T>;
end;
And here's a way to use it. On any class I can add a function like this (and I'm intentionally creating an enumerator that doesn't use a List<T> to show the power of this concept):
type Form1 = class(TForm)
protected
function Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
end;
// This is all that's needed to implement an enumerator!
function Form1.Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
var Current:Integer;
begin
Current := From - 1;
Result := TGenericAnonEnumFactory<Integer>.Create(
// This is the MoveNext implementation
function :Boolean
begin
Inc(Current);
Result := Current <= To;
end
,
// This is the GetCurrent implementation
function :Integer
begin
Result := Current;
end
);
end;
And here's how I'd use this new enumerator:
procedure Form1.Button1Click(Sender: TObject);
var N:Integer;
begin
for N in Numbers(3,10) do
Memo1.Lines.Add(IntToStr(N));
end;
See DeHL ( http://code.google.com/p/delphilhlplib/ ). You can write code that looks like this:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
Just like you can do in .NET (no syntax linq of course).
You approach is fine. I don't know of any better way.
Enumerator factory can also be implemented as a record instead of an interface.
Maybe you'll get some ideas here.
Delphi For in loop support requires on of the following: (From the Docs)
Primitive types that the compiler
recognizes, such as arrays, sets or
strings
Types that implement
IEnumerable
Types that implement the
GetEnumerator pattern as documented
in the Delphi Language Guide
If you look at Generics.Collections.pas you will find the implementation for TDictionary<TKey,TValue> where it has three enumerators for TKey, TValue, and TPair<TKey,TValue> types. Embarcadero shows that they have used verbose implementation.
You could do something like this:
unit Generics.AnonEnum;
interface
uses
SysUtils,
Generics.Defaults,
Generics.Collections;
type
TAnonEnumerator<T> = class(TEnumerator<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
TAnonEnumerable<T> = class(TEnumerable<T>)
protected
FGetCurrent : TFunc<TAnonEnumerator<T>,T>;
FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>;
function DoGetEnumerator: TEnumerator<T>; override;
public
Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>;
aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>);
end;
implementation
{ TEnumerable<T> }
constructor TAnonEnumerable<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerable<T>.DoGetEnumerator: TEnumerator<T>;
begin
result := TAnonEnumerator<T>.Create(FGetCurrent,FMoveNext);
end;
{ TAnonEnumerator<T> }
constructor TAnonEnumerator<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>;
aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>);
begin
FGetCurrent := aGetCurrent;
FMoveNext := aMoveNext;
end;
function TAnonEnumerator<T>.DoGetCurrent: T;
begin
result := FGetCurrent(self);
end;
function TAnonEnumerator<T>.DoMoveNext: Boolean;
begin
result := FMoveNext(Self);
end;
end.
This would allow you declare your Current and MoveNext methods anonymously.
You can do away with the factory and the interface if you add a GetEnumerator() function to your enumerator, like this:
TFilteredEnum = class
public
constructor Create(AList:TList<TMyObject>; AFilterValue:Integer);
function GetEnumerator: TFilteredEnum;
function MoveNext:Boolean; // This is where filtering happens
property Current: TMyObject;
end;
and just return self:
function TFilteredEnum.GetEnumerator: TSimpleEnumerator;
begin
result := Self;
end;
and Delphi will conveniently clean up your instance for you, just like it does any other enumerator:
var
L: TMyList;
E: TMyObject;
begin
for E in TFilteredEnum.Create(L, 7) do ;
end;
You can then extend your enumerator to use an anonymous method, which you can pass in the constructor:
TFilterFunction = reference to function (AObject: TMyObject): boolean;
TFilteredEnum = class
private
FFilterFunction: TFilterFunction;
public
constructor Create(AList:TList<TMyObject>; AFilterFunction: TFilterFunction);
...
end;
...
function TFilteredEnum.MoveNext: boolean;
begin
if FIndex >= FList.Count then
Exit(False);
inc(FIndex);
while (FIndex < FList.Count) and not FFilterFunction(FList[FIndex]) do
inc(FIndex);
result := FIndex < FList.Count;
end;
call it like this:
var
L:TMyList;
E:TMyObject;
begin
for E in TFilteredEnum.Create(L, function (AObject: TMyObject): boolean
begin
result := AObject.Value = 7;
end;
) do
begin
//do stuff here
end
end;
Then you could even make it a generic, but I wont do that here, my answer is long enough as it is.
N#
I use this approach...where the AProc performs the filter test.
TForEachDataItemProc = reference to procedure ( ADataItem: TDataItem; var AFinished: boolean );
procedure TDataItems.ForEachDataItem(AProc: TForEachDataItemProc);
var
AFinished: Boolean;
ADataItem: TDataItem;
begin
AFinished:= False;
for ADataItem in FItems.Values do
begin
AProc( ADataItem, AFinished );
if AFinished then
Break;
end;
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.