Discussion:
How to call class method by reference?
(too old to reply)
aaaa
2011-12-27 21:14:35 UTC
Permalink
Hi,
I have classes:
TDog, TCat, TFish.
Each one has a function named sing().
Each one should register itself in TManager class, like this:

TManager.Register('Dog', TDog);

Now I want to supply a string to TManager class to make the animal sing(),
like this:

m := TManager;
m.sing('Dog'); //should call TDog's sing() function.

Now the hard part:

1) I want the animals to register themself, so I don't want any ifs like
this in TManager:

if str = 'Dog' then TDog.sing();

I want to be able to remove TDog's unit from uses and I want all my code to
work fine with all the animals that are left.

2) The animals do not have a common base class, so I cannot do something
like:

type TAnimalClass = class of TAnimal;

var a: TAnimal;
ac: TAnimalClass;
begin
a := ac.Create;
a.sing();

How to solve this problem?

Best regards,
A
Jamie
2011-12-28 02:50:07 UTC
Permalink
Post by aaaa
Hi,
TDog, TCat, TFish.
Each one has a function named sing().
TManager.Register('Dog', TDog);
Now I want to supply a string to TManager class to make the animal
m := TManager;
m.sing('Dog'); //should call TDog's sing() function.
1) I want the animals to register themself, so I don't want any ifs like
if str = 'Dog' then TDog.sing();
I want to be able to remove TDog's unit from uses and I want all my code
to work fine with all the animals that are left.
2) The animals do not have a common base class, so I cannot do something
type TAnimalClass = class of TAnimal;
var a: TAnimal;
ac: TAnimalClass;
begin
a := ac.Create;
a.sing();
How to solve this problem?
Best regards,
A
not sure, it almost likes like school work?

maybe "SETS" or Enumerate types is what you want?

Or maybe just a TListObject ?

Jamie
aaaa
2011-12-28 11:42:43 UTC
Permalink
Post by Jamie
not sure, it almost likes like school work?
No, I already have master's degree :)
--
A
Jamie
2011-12-28 02:56:58 UTC
Permalink
Post by aaaa
Hi,
TDog, TCat, TFish.
Each one has a function named sing().
TManager.Register('Dog', TDog);
Now I want to supply a string to TManager class to make the animal
m := TManager;
m.sing('Dog'); //should call TDog's sing() function.
1) I want the animals to register themself, so I don't want any ifs like
if str = 'Dog' then TDog.sing();
I want to be able to remove TDog's unit from uses and I want all my code
to work fine with all the animals that are left.
2) The animals do not have a common base class, so I cannot do something
type TAnimalClass = class of TAnimal;
var a: TAnimal;
ac: TAnimalClass;
begin
a := ac.Create;
a.sing();
How to solve this problem?
Best regards,
A
This is delphi, it generates real code, not higher level script
code...

If you look at the "TOBJECT" in the help.

If TlistObject[x].ClassNameIs('TDOG') then...

Jamie
aaaa
2011-12-28 11:41:12 UTC
Permalink
Post by Jamie
This is delphi, it generates real code, not higher level script
code...
I know :)
Post by Jamie
If you look at the "TOBJECT" in the help.
If TlistObject[x].ClassNameIs('TDOG') then...
But I would need to put this in TManager which is not exactly what I want to
achieve.
You see- I want to create more animals and add them just by adding to uses
and without changing TManager.
I want to write TManager once and then never change it.

I already found a solution and here it is:

type TSingProc = procedure(Stream: TStream) of object;
var Animal: TAncestor;
AnimalClass: TAncestorClass;
SaveProc: TSingProc;
Method: TMethod;
begin
AnimalClass := GetByExt(Ext);
Animal := AnimalClass.Create;
Method.Code := Animal.MethodAddress('Sing');
Method.Data := Animal;
SingProc := TSingProc(Method);
SingProc(Stream);

Sing() has to be published in all animals.

I did lie a bit before (in order to describe the problem in a simple way).
All animals have a common base class but it is a system class so I cannot
modify it and it goes like this:

TAncestor -> TSomething ->TSomethingElse -> TDog
TAncestor -> TElse -> TFish

I tried doing:

TAnimalClass = class of TAncestor;
TAncestor = class(SystemFile.TAncestor)

and add Sing() that way, but it didn't work well- the compiler said
MyFile.TAncestor and SystemFile.TAncestor are not compatible,
class reference and TAnimalClass are not compatible and it didn't look like
I can do anything about it.

Best regards,
A
Hans-Peter Diettrich
2011-12-28 05:33:20 UTC
Permalink
Post by aaaa
2) The animals do not have a common base class, so I cannot do something
type TAnimalClass = class of TAnimal;
Then use interfaces.

type TDog = class(TInterfacedObject, IAnimal)

DoDi
aaaa
2011-12-28 11:26:44 UTC
Permalink
Post by Hans-Peter Diettrich
Then use interfaces.
type TDog = class(TInterfacedObject, IAnimal)
But my TDog is already based on a class and I don't want to change this.

Best regards,
A
Rudy Velthuis
2011-12-28 12:33:52 UTC
Permalink
Post by aaaa
Post by Hans-Peter Diettrich
Then use interfaces.
type TDog = class(TInterfacedObject, IAnimal)
But my TDog is already based on a class and I don't want to change this.
Then forget it. Or use extended RTTI, if that is available in your
version of Delphi. Note that extended RTTI means lookups at runtime, so
it is probably a lot slower than implementing interfaces (and your
executable may be a bit larger).

If you already have classes that don't implement the necessary
interfaces, then derive from them to give them interfaces or wrap them
in wrapper classes.

I'm not sure if class helpers can introduce interfaces. I doubt it,
since that would require changes in the structure of the already
existing helped class and class helpers can't do that.

But ideally, you work with interfaces and give each animal the
capabilities it has using interfaces.
--
Rudy Velthuis

"His mother should have thrown him away and kept the stork."
-- Mae West
Hans-Peter Diettrich
2011-12-28 14:47:28 UTC
Permalink
Post by aaaa
Post by Hans-Peter Diettrich
Then use interfaces.
type TDog = class(TInterfacedObject, IAnimal)
But my TDog is already based on a class and I don't want to change this.
Then add the interface support to that class (_AddRef etc.). You can use
the code in TInterfacedObject as a template.

Hint: your rating will be reduced to "a", if you don't know about using
interfaces in Delphi ;-)

DoDi
aaaa
2011-12-28 17:31:57 UTC
Permalink
Post by Hans-Peter Diettrich
Then add the interface support to that class (_AddRef etc.). You can use
the code in TInterfacedObject as a template.
I already did like Rudy Velthuis said. Interfaces seems much more elegant
than MethodAddress.
Everything works great now :)

Best regards,
A
Rudy Velthuis
2011-12-28 12:29:12 UTC
Permalink
Post by aaaa
Hi,
TDog, TCat, TFish.
Each one has a function named sing().
TManager.Register('Dog', TDog);
Now I want to supply a string to TManager class to make the animal
m := TManager;
m.sing('Dog'); //should call TDog's sing() function.
1) I want the animals to register themself, so I don't want any ifs
if str = 'Dog' then TDog.sing();
I want to be able to remove TDog's unit from uses and I want all my
code to work fine with all the animals that are left.
2) The animals do not have a common base class, so I cannot do
type TAnimalClass = class of TAnimal;
var a: TAnimal;
ac: TAnimalClass;
begin
a := ac.Create;
a.sing();
How to solve this problem?
If they don't derive from the same base class, you'll have to use
interfaces. Interfaces are orthogonal to any class hierarchy and
problems like yours are exactly what they solve.

type
ICanSing = interface
[GUID here] // use Ctrl+Shift+G to create one in the editor
procedure Sing;
end;

and implementing the interface for every animal that can sing (or fly
or whatever its capabilities):

TBird = class(TInterfacedObject, ICanSing, ICanFly, ...)
...
procedure Sing;
...
end;

Now you can check if the animal can sing by checking:

var
mySinger: ICanSing;

...

if Supports(myAnimal, ICanSing, mySinger) then
begin
...
mySinger.Sing;
...
end;
--
Rudy Velthuis

"I agree with the reforms, but I want nothing to change"
-- Ion Luca Caragiale, Romanian playwriter, 1880
aaaa
2011-12-28 13:31:52 UTC
Permalink
Post by Rudy Velthuis
If they don't derive from the same base class, you'll have to use
interfaces. Interfaces are orthogonal to any class hierarchy and
problems like yours are exactly what they solve.
type
ICanSing = interface
[GUID here] // use Ctrl+Shift+G to create one in the editor
procedure Sing;
end;
and implementing the interface for every animal that can sing (or fly
TBird = class(TInterfacedObject, ICanSing, ICanFly, ...)
...
procedure Sing;
...
end;
var
mySinger: ICanSing;
...
if Supports(myAnimal, ICanSing, mySinger) then
begin
...
mySinger.Sing;
...
end;
I have already solved it: news:4efb009f$0$1204$***@news.neostrada.pl

but these interfaces are something I will learn more about.
Somehow I never really needed them before... thanks!

Best regards,
A
Skybuck Flying
2011-12-29 08:02:32 UTC
Permalink
Some ways to solve it:

1. Language technical way: "class helpers"

2. Simply store a string + method pointer + object pointer in a list and
invoke the method when the string is found in the list.

3. I always stay away from interfaces like the plague ;)

Bye,
Skybuck.

Loading...