vladh
asked on
Delphi object inheritance question
Hello experts;
Please see the code below. I am attempting to create two classes; one TANODE (ancestor) and one TDNODE (descendant). Objects of these type will be part of the taclass "container". TACLASS has AddChild routine that adds these child nodes to container. It works fine on ancestor nodes. The problem comes when my descendant node is added to its container using AddChild routine, as marked in the code attached.
Could someone explain what am I doing wrong
Thanks
Vlad
Please see the code below. I am attempting to create two classes; one TANODE (ancestor) and one TDNODE (descendant). Objects of these type will be part of the taclass "container". TACLASS has AddChild routine that adds these child nodes to container. It works fine on ancestor nodes. The problem comes when my descendant node is added to its container using AddChild routine, as marked in the code attached.
Could someone explain what am I doing wrong
Thanks
Vlad
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
tanode = class
name : string;
parent : ^tanode;
public
constructor create;
destructor destroy;
end;
tdnode = class(tanode)
rdn : string;
public
constructor create;
end;
taclass = class
node : tanode;
desc : string;
function addchild(parent: tanode):tanode;
public
constructor create;
destructor destroy;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor tanode.create;
begin
inherited create;
end;
constructor tdnode.create;
begin
inherited create;
end;
destructor tanode.destroy;
begin
inherited destroy;
end;
constructor taclass.create;
begin
inherited create;
end;
destructor taclass.destroy;
begin
inherited destroy;
end;
function taclass.addchild(parent:tanode):tanode;
begin
if parent=nil then
begin
result := tanode.create;
result.name := 'root';
result.parent:= nil;
end else
begin
result := tanode.create;
result.name := 'child_of_'+parent.name;
result.parent := @parent;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var ac: taclass;
an, an1 : tanode;
dn, dn1 : tdnode;
begin
ac := taclass.create;
an := ac.addchild(nil);
label1.Caption := an.name;
an1 := ac.addchild(an);
label2.Caption := an1.name;
dn := tdnode(ac.addchild(nil)); <<<----- when addchild returns, dn is "inaccessible value"
dn.rdn := dn.name+'_RDN';
label3.Caption := dn.rdn;
end;
end.
My last suggestion is incomplete. If you're to cast the result to a tdnode, you MUST create a tdnode inside addchild function. If you want save from casting, then you should change the return to tdnode, but that's not mandatory in order for your code to work.
ASKER
twocandles
Thanks for the suggestions. I guess I am a bit confused about the way inheritance works. My understanding was that TDNODE should inherit all that's contained in TANODE; and according to at least one Delphi book, I should be able to pass descendants to the routines designed to work on the ancestor nodes. I was expecting that AddChild routine would modify all fields common to both nodes, return TANODE which I would then recast into TDNODE and then simply change the RDN field (specific to TDNODE). It obviously is not working this way. Well then how can I take advantage of inheritance in this case? My understanding was that I can define the most common fields and routines in the top ancestor and just re-use them in descendants without rewriting - seems it's not the case?
Thanks for the suggestions. I guess I am a bit confused about the way inheritance works. My understanding was that TDNODE should inherit all that's contained in TANODE; and according to at least one Delphi book, I should be able to pass descendants to the routines designed to work on the ancestor nodes. I was expecting that AddChild routine would modify all fields common to both nodes, return TANODE which I would then recast into TDNODE and then simply change the RDN field (specific to TDNODE). It obviously is not working this way. Well then how can I take advantage of inheritance in this case? My understanding was that I can define the most common fields and routines in the top ancestor and just re-use them in descendants without rewriting - seems it's not the case?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
twocandles,
it seems I should code a separate constructor for my descendant since I can use ascendants creator to return the descendant as the result. So it seems my descendant can not directly reuse the ascendants code for functions - or routines in ascendant class that return a result of type TANODE.
Looks like I have to rewrite constructors every time a descendant class is created. It's also a bit unclear at this point how I can reuse ascendants code for descendant class if I can not return the descendant class type from ancestors code (I am trying to make a generic, base ancestor class that would not be specifically aware of descendants but would still work for the common tasks such as add, delete, rename etc and the descendant would take care of that class-specific fields)
I wouldn't say I am new to Delphi, I've been using it for many years for quick and dirty programs to help with my job but I never took the time to learn OOP properly (used procedural approach for my code). I am trying to learn OOP now and that's where this question is coming from.
Geert,
Your suggestion is interesting although it doesn't answer my question (the questions purpose was not production code but rather learning of OOP).
I will need to experiment with the code a some more and will have to get back to this a bit later - time is a little tight right now. Again, thank you for your time and suggestions.
Vlad
it seems I should code a separate constructor for my descendant since I can use ascendants creator to return the descendant as the result. So it seems my descendant can not directly reuse the ascendants code for functions - or routines in ascendant class that return a result of type TANODE.
Looks like I have to rewrite constructors every time a descendant class is created. It's also a bit unclear at this point how I can reuse ascendants code for descendant class if I can not return the descendant class type from ancestors code (I am trying to make a generic, base ancestor class that would not be specifically aware of descendants but would still work for the common tasks such as add, delete, rename etc and the descendant would take care of that class-specific fields)
I wouldn't say I am new to Delphi, I've been using it for many years for quick and dirty programs to help with my job but I never took the time to learn OOP properly (used procedural approach for my code). I am trying to learn OOP now and that's where this question is coming from.
Geert,
Your suggestion is interesting although it doesn't answer my question (the questions purpose was not production code but rather learning of OOP).
I will need to experiment with the code a some more and will have to get back to this a bit later - time is a little tight right now. Again, thank you for your time and suggestions.
Vlad
For the constructors, you don't have to override them (reprogram them) for each descendant unless you want extra behaviour for the descendant. For Delphi the constructor it's only a function that it's called when you create the instance. For example:
var
ta: tanode;
td: tdnode;
(...)
// You would have a tanode here. The tanode constructor would be called
ta := tanode.Create;
// You would have a tdnode here. The tdnode constructor
// would be called (if any), and then the tanode constructor (if you don't
// specify the inherited call, Delphi does it for you)
td := tdnode.Create;
// You would have a tdnode here. tdnode constructor would be called and then tanode constrcutor. But you would be able to access only tanode methods because of the
// use of a tanode variable. In this case you would be able to cast to tdnode because
// the right constructor is called
ta := tdnode.Create;
About creating descendant objects from an ancestor, it's not a Delphi "problem" but OOP phylosophy. An object can't use anything it's not aware of. I've come across this problem and Delphi provides a "workaround": using class of. Class keyword it's like a "reference to object" (not instance).
var
ta: tanode;
td: tdnode;
(...)
// You would have a tanode here. The tanode constructor would be called
ta := tanode.Create;
// You would have a tdnode here. The tdnode constructor
// would be called (if any), and then the tanode constructor (if you don't
// specify the inherited call, Delphi does it for you)
td := tdnode.Create;
// You would have a tdnode here. tdnode constructor would be called and then tanode constrcutor. But you would be able to access only tanode methods because of the
// use of a tanode variable. In this case you would be able to cast to tdnode because
// the right constructor is called
ta := tdnode.Create;
About creating descendant objects from an ancestor, it's not a Delphi "problem" but OOP phylosophy. An object can't use anything it's not aware of. I've come across this problem and Delphi provides a "workaround": using class of. Class keyword it's like a "reference to object" (not instance).
vladh
>>I am trying to learn OOP now and that's where this question is coming from.
I was trying to show a solution using OOP
This is also learning what allready exists and trying to use that.
Once you know what allready exists and how to use it, the next step is extending it
>>I am trying to learn OOP now and that's where this question is coming from.
I was trying to show a solution using OOP
This is also learning what allready exists and trying to use that.
Once you know what allready exists and how to use it, the next step is extending it
ASKER
sorry for the delay - I will be evaluating the answer shortly
Thanks
Thanks
ASKER
I am closing the question manually and I do not wish for it to be deleted
dn := tdnode(ac.addchild(nil));
but in the addchild method you're creating a tanode. Thus,
dn.rdn := dn.name+'_RDN';
is not a valid statement because the result of addchild does not contain a rdn member.
I would change the function addChild to return a tdnode or, if you don't want to change that, create a tdnode instead of tanode (though you eventually return a tanode, but the cast won't be a problem)