Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Delphi object inheritance question

Posted on 2009-04-14
11
Medium Priority
?
754 Views
Last Modified: 2013-11-23
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
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.

Open in new window

0
Comment
Question by:vladh
  • 4
  • 4
  • 2
11 Comments
 
LVL 6

Expert Comment

by:twocandles
ID: 24145358
There's a problem in your code: your casting your result to the descendant class here

   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)


0
 
LVL 6

Expert Comment

by:twocandles
ID: 24145379
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.
0
 
LVL 3

Author Comment

by:vladh
ID: 24154391
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?
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 6

Accepted Solution

by:
twocandles earned 500 total points
ID: 24154944
seems it's not the case?

Yes, it is, and the book you read is right: you can use a descendant whenever an ancestor is required,  but not the opposite.

Inside AddChild you're passing the parameter "parent" but you're creating a tanode for the result. This result cannot be downcasted to a tdnode. It would be possible to pass in a tdnode (inside the function it would act as a tanode), return the input parameter (result := parent) and then you could cast back to tdnode.

The summary would be that you can cast to a descendant when the object pointed to by the varialbe was created using the descendant constructor.

And if you're starting with Delphi, in the line:

      result.parent := @parent;

use this instead

      result.parent := parent;

Object variables are already pointers, you don't need to get its address.

0
 
LVL 38

Assisted Solution

by:Geert Gruwez
Geert Gruwez earned 500 total points
ID: 24156709
what you seem to needing is a collection of items
and each item in itself can own a collection of items ...

i modified the code so it inherits from a TOwnedCollection and a TCollectionItem

The trick is in here :
constructor TANodes.Create(AOwner: TPersistent);
begin
  //inherited Create(AOwner, TANode);
  inherited Create(AOwner, TDNode);
end;

using
inherited Create(AOwner, TDNode);
you can TANode and TDNode

using
inherited Create(AOwner, TANode);
you can only add TANode

you can upcast, but not downcast

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;
 
type
  TANodes = class;
 
  TANode = class(TCollectionItem)
  private
    fName: string;
    fParent: TANode;
    fChildren: TANodes;
    function GetChildNodes: TANodes;
    function GetChildren(aName: string): TANode;
    function GetItems(Index: integer): TANode;
    function GetParent: TANode;
  protected
    property ChildNodes: TANodes read GetChildNodes;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    property Name: string read fName write fName;
    property Parent: TANode read GetParent;
    property Children[aName: string]: TANode read GetChildren;
    property Items[Index: integer]: TANode read GetItems;
  end;
 
  TDNode = class(TANode)
  private
    fRdn: string;
  public
    property Rdn: string read fRdn write fRdn;
  end;
 
  TANodes = class(TOwnedCollection)
  private
    fDesc: string;
  public
    constructor Create(AOwner: TPersistent);
    function AddChild(Parent: TANode): TANode;
    property Desc: string read fDesc write fDesc;
  end;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
{ TANode }
 
constructor TANode.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  fName := 'root';
  fParent := nil;
  fChildren := nil;
end;
 
destructor TANode.Destroy;
begin
  FreeAndNil(fChildren);
  inherited Destroy;
end;
 
function TANode.GetChildNodes: TANodes;
begin
  if not Assigned(fChildren) then
    fChildren := TANodes.Create(Self);
  Result := fChildren;
end;
 
function TANode.GetChildren(aName: string): TANode;
var I: Integer;
begin
  Result := nil;
  with GetChildNodes do
    for I := 0 to Count-1 do
      if SameText(TANode(Items[I]).Name, aName) then
      begin
        Result := TANode(Items[I]);
        Break;
      end;
end;
 
function TANode.GetItems(Index: integer): TANode;
begin
  Result := TANode(GetChildNodes.Items[Index]);
end;
 
function TANode.GetParent: TANode;
begin
  Result := TANode(Collection.Owner);
end;
 
{ TANodes }
 
function TANodes.AddChild(Parent: TANode): TANode;
begin
  if Assigned(Parent) then
  begin
    Result := TANode(Parent.ChildNodes.Add);
    Result.Name := 'child of ' + Parent.Name;
  end else
    Result := TANode(Add);
end;
 
constructor TANodes.Create(AOwner: TPersistent);
begin
  //inherited Create(AOwner, TANode);
  inherited Create(AOwner, TDNode);
end;
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
var ac: TANodes;
    an, an1 : TANode;
    dn, dn1 : TDNode;
begin
   ac := TANodes.Create(Self);
   an := ac.addchild(nil);
   Memo1.Lines.Add('An.Name = ' + an.name);
   //label1.Caption := an.name;
   an1 := ac.addchild(an);
   Memo1.Lines.Add('an1.Name = ' + an1.Name);
   //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;
   Memo1.Lines.Add('dn.Name = ' + dn.Name);
   Memo1.Lines.Add('dn.RDN = ' + dn.rdn);
end;
 
end.

Open in new window

0
 
LVL 3

Author Comment

by:vladh
ID: 24205313
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
0
 
LVL 6

Expert Comment

by:twocandles
ID: 24205641
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).

0
 
LVL 38

Expert Comment

by:Geert Gruwez
ID: 24212447
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
0
 
LVL 3

Author Comment

by:vladh
ID: 25993469
sorry for the delay - I will be evaluating the answer shortly
Thanks
0
 
LVL 3

Author Comment

by:vladh
ID: 34273586
I am closing the question manually and I do not wish for it to be deleted
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In our object-oriented world the class is a minimal unit, a brick for constructing our applications. It is an abstraction and we know well how to use it. In well-designed software we are not usually interested in knowing how objects look in memory. …
Jaspersoft Studio is a plugin for Eclipse that lets you create reports from a datasource.  In this article, we'll go over creating a report from a default template and setting up a datasource that connects to your database.
The viewer will learn how to synchronize PHP projects with a remote server in NetBeans IDE 8.0 for Windows.
The viewer will learn how to use and create new code templates in NetBeans IDE 8.0 for Windows.

885 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question