Solved

Delphi object inheritance question

Posted on 2009-04-14
11
720 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
 
LVL 6

Accepted Solution

by:
twocandles earned 125 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 36

Assisted Solution

by:Geert Gruwez
Geert Gruwez earned 125 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
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
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 36

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

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

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. …
Here is a helpful source code for C++ Builder programmers that allows you to manage and manipulate HTML content from C++ code, while also handling HTML events like onclick, onmouseover, ... Some objects defined and used in this source include: …
The viewer will learn how to use and create keystrokes 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.

758 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

Need Help in Real-Time?

Connect with top rated Experts

23 Experts available now in Live!

Get 1:1 Help Now